Proper RTC emulation based on work by Mahod.

Updated README.md.
Updated AMD makefile.
This commit is contained in:
OBattler
2016-07-11 01:27:23 +02:00
parent 7b8c9a1522
commit 0236637c5a
12 changed files with 570 additions and 399 deletions

View File

@@ -1,7 +1,7 @@
# PCem Experimental [![Build Status](http://citadel.rol.im:8080/job/PCem-Experimental/badge/icon)](http://citadel.rol.im:8080/job/PCem-Experimental)
PCem Experimental is an unofficial branch of the PCem emulator, which aims to emulate IBM compatible machines from 1981-2000 period. This branch adds several emulated motherboards.
# PCem Unofficial [![Build Status](http://polar.rol.im:8080/job/PCem-Unofficial/badge/icon)](http://polar.rol.im:8080/job/PCem-Unofficial)
PCem Unofficial is an unofficial branch of the PCem emulator, which aims to emulate IBM compatible machines from 1981-2000 period. This branch adds several emulated motherboards.
---
Keep in touch with the PCem Experimental community:
Keep in touch with the PCem Unoffical community:
[![Visit our IRC channel](https://kiwiirc.com/buttons/irc.rol.im/pcem-x.png)](https://kiwiirc.com/client/irc.rol.im/?nick=pcem|?#pcem-x)

View File

@@ -8,7 +8,7 @@ OBJ = 386.o 386_dynarec.o 386_dynarec_ops.o 808x.o acer386sx.o acerm3a.o ali1429
device.o disc.o disc_fdi.o disc_img.o disc_sector.o dma.o fdc.o fdc37c665.o fdc37c932fr.o fdd.o fdi2raw.o gameport.o headland.o i430hx.o i430lx.o i430fx.o \
i430nx.o i430vx.o i440fx.o ide.o intel.o intel_flash.o io.o jim.o joystick_standard.o joystick_sw_pad.o keyboard.o keyboard_amstrad.o keyboard_at.o \
keyboard_olim24.o keyboard_pcjr.o keyboard_xt.o lpt.o mcr.o mem.o memregs.o model.o mouse.o mouse_ps2.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o rtc.o \
scat.o serial.o sis496.o sis85c471.o sio.o sound.o sound_ad1848.o sound_adlib.o sound_adlibgold.o sound_cms.o \
sound_dbopl.o sound_emu8k.o sound_gus.o sound_mpu401_uart.o sound_opl.o sound_pas16.o sound_ps1.o sound_pssj.o sound_resid.o \
sound_sb.o sound_sb_dsp.o sound_sn76489.o sound_speaker.o sound_ssi2001.o sound_wss.o sound_ym7128.o \
@@ -20,7 +20,7 @@ OBJ = 386.o 386_dynarec.o 386_dynarec_ops.o 808x.o acer386sx.o acerm3a.o ali1429
vid_svga_render.o vid_tandy.o vid_tandysl.o vid_tgui9440.o vid_tkd8001_ramdac.o vid_tvga.o vid_unk_ramdac.o \
vid_vga.o vid_voodoo.o video.o w83877f.o wd76c10.o win.o win-config.o win-d3d.o win-d3d-fs.o win-ddraw.o \
win-ddraw-fs.o win-ddraw-screenshot.o win-deviceconfig.o win-hdconf.o win-joystick.o win-joystickconfig.o win-keyboard.o win-midi.o win-mouse.o \
win-status.o win-time.o win-video.o x86seg.o x87.o xtide.o pc.res
win-status.o win-video.o x86seg.o x87.o xtide.o pc.res
DBOBJ = dbopl.o vid_cga_comp.o
SIDOBJ = convolve.o convolve-sse.o envelope.o extfilt.o filter.o pot.o sid.o voice.o wave6581__ST.o wave6581_P_T.o wave6581_PS_.o wave6581_PST.o wave8580__ST.o wave8580_P_T.o wave8580_PS_.o wave8580_PST.o wave.o
SLIRPOBJ = bootp.o ip_icmp.o misc.o socket.o tcp_timer.o cksum.o ip_input.o queue.o tcp_input.o tftp.o debug.o ip_output.o sbuf.o tcp_output.o udp.o if.o mbuf.o slirp.o tcp_subr.o

View File

@@ -8,7 +8,7 @@ OBJ = 386.o 386_dynarec.o 386_dynarec_ops.o 808x.o acer386sx.o acerm3a.o ali1429
device.o disc.o disc_fdi.o disc_img.o disc_sector.o dma.o fdc.o fdc37c665.o fdc37c932fr.o fdd.o fdi2raw.o gameport.o headland.o i430hx.o i430lx.o i430fx.o \
i430nx.o i430vx.o i440fx.o ide.o intel.o intel_flash.o io.o jim.o joystick_standard.o joystick_sw_pad.o keyboard.o keyboard_amstrad.o keyboard_at.o \
keyboard_olim24.o keyboard_pcjr.o keyboard_xt.o lpt.o mcr.o mem.o memregs.o model.o mouse.o mouse_ps2.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o rtc.o \
scat.o serial.o sis496.o sis85c471.o sio.o sound.o sound_ad1848.o sound_adlib.o sound_adlibgold.o sound_cms.o \
sound_dbopl.o sound_emu8k.o sound_gus.o sound_mpu401_uart.o sound_opl.o sound_pas16.o sound_ps1.o sound_pssj.o sound_resid.o \
sound_sb.o sound_sb_dsp.o sound_sn76489.o sound_speaker.o sound_ssi2001.o sound_wss.o sound_ym7128.o \
@@ -20,7 +20,7 @@ OBJ = 386.o 386_dynarec.o 386_dynarec_ops.o 808x.o acer386sx.o acerm3a.o ali1429
vid_svga_render.o vid_tandy.o vid_tandysl.o vid_tgui9440.o vid_tkd8001_ramdac.o vid_tvga.o vid_unk_ramdac.o \
vid_vga.o vid_voodoo.o video.o w83877f.o wd76c10.o win.o win-config.o win-d3d.o win-d3d-fs.o win-ddraw.o \
win-ddraw-fs.o win-ddraw-screenshot.o win-deviceconfig.o win-hdconf.o win-joystick.o win-joystickconfig.o win-keyboard.o win-midi.o win-mouse.o \
win-status.o win-time.o win-video.o x86seg.o x87.o xtide.o pc.res
win-status.o win-video.o x86seg.o x87.o xtide.o pc.res
DBOBJ = dbopl.o vid_cga_comp.o
SIDOBJ = convolve.o convolve-sse.o envelope.o extfilt.o filter.o pot.o sid.o voice.o wave6581__ST.o wave6581_P_T.o wave6581_PS_.o wave6581_PST.o wave8580__ST.o wave8580_P_T.o wave8580_PS_.o wave8580_PST.o wave.o
SLIRPOBJ = bootp.o ip_icmp.o misc.o socket.o tcp_timer.o cksum.o ip_input.o queue.o tcp_input.o tftp.o debug.o ip_output.o sbuf.o tcp_output.o udp.o if.o mbuf.o slirp.o tcp_subr.o

View File

@@ -8,28 +8,28 @@ OBJ = 386.o 386_dynarec.o 386_dynarec_ops.o 808x.o acer386sx.o acerm3a.o ali1429
device.o disc.o disc_fdi.o disc_img.o disc_sector.o dma.o fdc.o fdc37c665.o fdc37c932fr.o fdd.o fdi2raw.o gameport.o headland.o i430hx.o i430lx.o i430fx.o \
i430nx.o i430vx.o i440fx.o ide.o intel.o intel_flash.o io.o jim.o joystick_standard.o joystick_sw_pad.o keyboard.o keyboard_amstrad.o keyboard_at.o \
keyboard_olim24.o keyboard_pcjr.o keyboard_xt.o lpt.o mcr.o mem.o memregs.o model.o mouse.o mouse_ps2.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o \
mouse_serial.o ne2000.o neat.o nethandler.o nmi.o nvr.o olivetti_m24.o opti.o pc.o pc87306.o pci.o pic.o piix.o pit.o ppi.o ps1.o rom.o rtc.o \
scat.o serial.o sis496.o sis85c471.o sio.o sound.o sound_ad1848.o sound_adlib.o sound_adlibgold.o sound_cms.o \
sound_dbopl.o sound_emu8k.o sound_gus.o sound_mpu401_uart.o sound_opl.o sound_pas16.o sound_ps1.o sound_pssj.o sound_resid.o \
sound_sb.o sound_sb_dsp.o sound_sn76489.o sound_speaker.o sound_ssi2001.o sound_wss.o sound_ym7128.o \
soundopenal.o tandy_eeprom.o tandy_rom.o timer.o um8669f.o vid_ati_eeprom.o vid_ati_mach64.o vid_ati18800.o \
vid_ati28800.o vid_ati68860_ramdac.o vid_cga.o vid_cga_comp.o vid_cl5429.o vid_ega.o vid_et4000.o \
vid_ati28800.o vid_ati68860_ramdac.o vid_cga.o vid_cl5429.o vid_ega.o vid_et4000.o \
vid_et4000w32.o vid_hercules.o vid_icd2061.o vid_ics2595.o vid_incolor.o vid_mda.o \
vid_olivetti_m24.o vid_oti067.o vid_paradise.o vid_pc1512.o vid_pc1640.o vid_pc200.o \
vid_pcjr.o vid_ps1_svga.o vid_s3.o vid_s3_virge.o vid_sdac_ramdac.o vid_stg_ramdac.o vid_svga.o \
vid_svga_render.o vid_tandy.o vid_tandysl.o vid_tgui9440.o vid_tkd8001_ramdac.o vid_tvga.o vid_unk_ramdac.o \
vid_vga.o vid_voodoo.o video.o w83877f.o wd76c10.o win.o win-config.o win-d3d.o win-d3d-fs.o win-ddraw.o \
win-ddraw-fs.o win-ddraw-screenshot.o win-deviceconfig.o win-hdconf.o win-joystick.o win-joystickconfig.o win-keyboard.o win-midi.o win-mouse.o \
win-status.o win-time.o win-video.o x86seg.o x87.o xtide.o pc.res
FMOBJ = dbopl.o
win-status.o win-video.o x86seg.o x87.o xtide.o pc.res
DBOBJ = dbopl.o vid_cga_comp.o
SIDOBJ = convolve.o convolve-sse.o envelope.o extfilt.o filter.o pot.o sid.o voice.o wave6581__ST.o wave6581_P_T.o wave6581_PS_.o wave6581_PST.o wave8580__ST.o wave8580_P_T.o wave8580_PS_.o wave8580_PST.o wave.o
SLIRPOBJ = bootp.o ip_icmp.o misc.o socket.o tcp_timer.o cksum.o ip_input.o queue.o tcp_input.o tftp.o debug.o ip_output.o sbuf.o tcp_output.o udp.o if.o mbuf.o slirp.o tcp_subr.o
LIBS = -mwindows -lwinmm -lopenal.dll -lopenal -lddraw -ldinput8 -ldxguid -ld3d9 -ld3dx9 -lwsock32 -liphlpapi -lstdc++ -static-libstdc++ -static-libgcc -static
PCem.exe: $(OBJ) $(FMOBJ) $(SIDOBJ) $(SLIRPOBJ)
$(CC) $(OBJ) $(FMOBJ) $(SIDOBJ) $(SLIRPOBJ) -o "PCem.exe" $(LIBS)
PCem.exe: $(OBJ) $(DBOBJ) $(SIDOBJ) $(SLIRPOBJ)
$(CC) $(OBJ) $(DBOBJ) $(SIDOBJ) $(SLIRPOBJ) -o "PCem.exe" $(LIBS)
strip "PCem.exe"
all : PCem.exe

View File

@@ -1,48 +0,0 @@
#include <sys/time.h>
#include <time.h>
#include "ibm.h"
#include "nvr.h"
void time_get(char *nvrram)
{
int c,d;
uint8_t baknvr[10];
time_t cur_time;
struct tm cur_time_tm;
memcpy(baknvr,nvrram,10);
cur_time = time(NULL);
localtime_r(&cur_time, &cur_time_tm);
d = cur_time_tm.tm_sec % 10;
c = cur_time_tm.tm_sec / 10;
nvrram[0] = d | (c << 4);
d = cur_time_tm.tm_min % 10;
c = cur_time_tm.tm_min / 10;
nvrram[2] = d | (c << 4);
d = cur_time_tm.tm_hour % 10;
c = cur_time_tm.tm_hour / 10;
nvrram[4] = d | (c << 4);
d = cur_time_tm.tm_wday % 10;
c = cur_time_tm.tm_wday / 10;
nvrram[6] = d | (c << 4);
d = cur_time_tm.tm_mday % 10;
c = cur_time_tm.tm_mday / 10;
nvrram[7] = d | (c << 4);
d = cur_time_tm.tm_mon % 10;
c = cur_time_tm.tm_mon / 10;
nvrram[8] = d | (c << 4);
d = cur_time_tm.tm_year % 10;
c = (cur_time_tm.tm_year / 10) % 10;
nvrram[9] = d | (c << 4);
if (baknvr[0] != nvrram[0] ||
baknvr[2] != nvrram[2] ||
baknvr[4] != nvrram[4] ||
baknvr[6] != nvrram[6] ||
baknvr[7] != nvrram[7] ||
baknvr[8] != nvrram[8] ||
baknvr[9] != nvrram[9])
nvrram[0xA] |= 0x80;
}

392
src/nvr.c
View File

@@ -4,6 +4,7 @@
#include "nvr.h"
#include "pic.h"
#include "timer.h"
#include "rtc.h"
int oldromset;
int nvrmask=63;
@@ -14,230 +15,16 @@ int nvr_dosave = 0;
static int nvr_onesec_time = 0, nvr_onesec_cnt = 0;
int enable_sync = 0;
#define second internal_time[0]
#define minute internal_time[1]
#define hour internal_time[2]
#define day internal_time[3]
#define month internal_time[4]
#define year internal_time[5]
int internal_time[6];
int days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static int is_leap(int org_year)
{
if (org_year % 400 == 0) return 1;
if (org_year % 100 == 0) return 0;
if (org_year % 4 == 0) return 1;
return 0;
}
static int get_days(int org_month, int org_year)
{
if (org_month != 2)
{
return days_in_month[org_month];
}
else
{
return is_leap(org_year) ? 29 : 28;
}
}
static int convert_to_bcd(int number)
{
int n1, n2;
n1 = number % 10;
n2 = number - n1;
n2 /= 10;
n2 <<= 4;
return (n2 | n1);
}
static int convert_from_bcd(int number)
{
int n1, n2;
n1 = number & 0xF;
n2 = number >> 4;
n2 *= 10;
return (n2 + n1);
}
static int final_form(int isbcd, int number)
{
return isbcd ? convert_to_bcd(number) : number;
}
static int original_form(int isbcd, int number)
{
return isbcd ? convert_from_bcd(number) : number;
}
static void nvr_recalc_clock()
{
if (second == 60)
{
second = 0;
minute++;
}
if (minute == 60)
{
minute = 0;
hour++;
}
if (hour == 24)
{
hour = 0;
day++;
}
if (day == (get_days(month, year) + 1))
{
day = 1;
month++;
}
if (month == 13)
{
month = 1;
year++;
}
nvr_dosave = 1;
}
static void nvr_update_internal_clock()
{
second++;
nvr_recalc_clock();
}
void nvr_add_10sec()
{
time_sleep(10000);
if (!enable_sync)
{
second+=10;
nvr_recalc_clock();
}
}
static int to_12_hour(int org_hour)
{
int hour2 = org_hour;
hour2 %= 12;
if (!hour2) hour2 = 12;
return hour2;
}
static int from_12_hour(int org_hour)
{
int hour2 = org_hour & 0x7F;
if (hour2 == 12) hour2 = 0;
if (hour & 0x80) hour2 += 12;
return hour2;
}
static int week_day()
{
int day_of_month = day;
int month2 = month;
int year2 = year % 100;
int century = ((year - year2) / 100) % 4;
int sum = day_of_month + month2 + year2 + century;
/* (sum mod 7) gives 0 for Saturday, we need it for Monday, so +5 */
int raw_wd = ((sum + 5) % 7);
/* +1 so 1 = Monday, 7 = Sunday */
return raw_wd + 1;
}
/* Called on every get time. */
static void set_registers()
{
int is24hour = (nvrram[0xB] & 2) ? 1 : 0;
int isbcd = (nvrram[0xB] & 4) ? 0 : 1;
uint8_t baknvr[10];
memcpy(baknvr,nvrram,10);
if (AMSTRAD)
{
is24hour = 1;
isbcd = 1;
}
nvrram[0] = final_form(isbcd, second);
nvrram[2] = final_form(isbcd, minute);
nvrram[4] = is24hour ? final_form(isbcd, hour) : final_form(isbcd, to_12_hour(hour));
nvrram[6] = week_day();
nvrram[7] = final_form(isbcd, day);
nvrram[8] = final_form(isbcd, month);
nvrram[9] = final_form(isbcd, (year % 100));
if (baknvr[0] != nvrram[0] ||
baknvr[2] != nvrram[2] ||
baknvr[4] != nvrram[4] ||
baknvr[6] != nvrram[6] ||
baknvr[7] != nvrram[7] ||
baknvr[8] != nvrram[8] ||
baknvr[9] != nvrram[9])
nvrram[0xA]|=0x80;
}
/* Called on NVR load and write. */
static void get_registers()
{
int is24hour = (nvrram[0xB] & 2) ? 1 : 0;
int isbcd = (nvrram[0xB] & 4) ? 0 : 1;
int temp_hour = 0;
if (AMSTRAD)
{
is24hour = 1;
isbcd = 1;
}
second = original_form(isbcd, nvrram[0]);
minute = original_form(isbcd, nvrram[2]);
hour = is24hour ? original_form(isbcd, nvrram[4]) : from_12_hour(original_form(isbcd, nvrram[4]));
day = original_form(isbcd, nvrram[7]);
month = original_form(isbcd, nvrram[8]);
year = original_form(isbcd, nvrram[9]) + 1900;
}
void getnvrtime()
{
if (enable_sync)
{
/* Get time from host. */
time_get(nvrram);
}
else
{
/* Get time from internal clock. */
set_registers();
}
}
void update_sync()
{
if (enable_sync)
{
/* Get time from host. */
time_get(nvrram);
}
else
{
/* Save time to registers but keep it as is. */
get_registers();
}
time_get(nvrram);
}
void nvr_recalc()
{
int c;
int newrtctime;
c=1<<((nvrram[0xA]&0xF)-1);
c=1<<((nvrram[RTCREGA]&(RTCRS0|RTCRS1|RTCRS2|RTCRS3))-1);
newrtctime=(int)(RTCCONST * c * (1 << TIMER_SHIFT));
if (rtctime>newrtctime) rtctime=newrtctime;
}
@@ -245,42 +32,80 @@ void nvr_recalc()
void nvr_rtc(void *p)
{
int c;
if (!(nvrram[0xA]&0xF))
if (!(nvrram[RTCREGA]&(RTCRS0|RTCRS1|RTCRS2|RTCRS3)))
{
rtctime=0x7fffffff;
return;
}
c=1<<((nvrram[0xA]&0xF)-1);
c=1<<((nvrram[RTCREGA]&(RTCRS0|RTCRS1|RTCRS2|RTCRS3))-1);
rtctime += (int)(RTCCONST * c * (1 << TIMER_SHIFT));
// pclog("RTCtime now %f\n",rtctime);
nvrram[0xC] |= 0x40;
if (nvrram[0xB]&0x40)
nvrram[RTCREGC] |= RTCPF;
if (nvrram[RTCREGB]&RTCPIE)
{
nvrram[0xC]|=0x80;
nvrram[RTCREGC]|=RTCIRQF;
if (AMSTRAD) picint(2);
else picint(0x100);
// pclog("RTC int\n");
}
}
int nvr_update_status = 0;
#define ALARM_DONTCARE 0xc0
int nvr_check_alarm(int nvraddr)
{
return (nvrram[nvraddr + 1] == nvrram[nvraddr] || (nvrram[nvraddr + 1] & ALARM_DONTCARE) == ALARM_DONTCARE);
}
void nvr_onesec(void *p)
{
nvr_onesec_cnt++;
if (nvr_onesec_cnt >= 100)
if (nvr_onesec_cnt >= 32768)
{
nvr_onesec_cnt = 0;
/* If sync is disabled, move internal clock ahead by 1 second. */
if (!enable_sync) nvr_update_internal_clock();
nvrram[0xC] |= 0x10;
if (nvrram[0xB] & 0x10)
/* If sync is disabled, move internal clock ahead by 1 second. */
if (!(nvrram[RTCREGB] & RTCSET))
{
nvrram[0xC] |= 0x80;
if (AMSTRAD) picint(2);
else picint(0x100);
nvr_update_status = RTCUIP;
if (!enable_sync) rtc_tick();
getnvrtime();
nvr_dosave = 1;
}
// pclog("RTC onesec\n");
}
nvr_onesec_time += (int)(10000 * TIMER_USEC);
else if (nvr_onesec_cnt == 73) /* 73 of our cycles means 244+1984 us = update in progress time per the specification. */
{
if (!(nvrram[RTCREGB] & RTCSET))
{
/* Clear update status. */
nvr_update_status = 0;
if (nvr_check_alarm(RTCSECONDS) && nvr_check_alarm(RTCMINUTES) && nvr_check_alarm(RTCHOURS))
{
nvrram[RTCREGC] |= RTCAF;
if (nvrram[RTCREGB] & RTCAIE)
{
nvrram[RTCREGC] |= RTCIRQF;
if (AMSTRAD) picint(2);
else picint(0x100);
}
}
/* The flag and interrupt should be issued on update ended, not started. */
nvrram[RTCREGC] |= RTCUF;
if (nvrram[RTCREGB] & RTCUIE)
{
nvrram[RTCREGC] |= RTCIRQF;
if (AMSTRAD) picint(2);
else picint(0x100);
}
// pclog("RTC onesec\n");
}
}
/* This is correct! The real RTC's one second timer operates at 32768 Hz, not 100 Hz! */
nvr_onesec_time += (int)((1000000.0 / 32768.0) * TIMER_USEC);
}
void writenvr(uint16_t addr, uint8_t val, void *priv)
@@ -289,45 +114,50 @@ void writenvr(uint16_t addr, uint8_t val, void *priv)
// printf("Write NVR %03X %02X %02X %04X:%04X %i\n",addr,nvraddr,val,cs>>4,pc,ins);
if (addr&1)
{
if (nvraddr==RTCREGC || nvraddr==RTCREGD) return; /* Registers C and D are read-only. There's no reason to continue. */
// if (nvraddr == 0x33) pclog("NVRWRITE33 %02X %04X:%04X %i\n",val,CS,pc,ins);
old = nvrram[nvraddr];
if (nvraddr >= 0xe && nvrram[nvraddr] != val)
if (nvraddr > RTCREGD && nvrram[nvraddr] != val)
nvr_dosave = 1;
// if (nvraddr==0xB) update_reg_0B(val);
if (nvraddr!=0xC && nvraddr!=0xD) nvrram[nvraddr]=val;
old = nvrram[nvraddr];
nvrram[nvraddr]=val;
/* If not syncing the time with the host, we need to update our internal clock on write. */
if (!enable_sync)
{
switch(nvraddr)
{
case 0:
case 2:
case 4:
case 6:
case 7:
case 8:
case 9:
if (old != val)
{
get_registers();
nvr_dosave = 1;
}
return;
}
}
if (nvraddr==0xA)
if (nvraddr==RTCREGA)
{
// pclog("NVR rate %i\n",val&0xF);
if (val&0xF)
if (val&(RTCRS0|RTCRS1|RTCRS2|RTCRS3))
{
c=1<<((val&0xF)-1);
c=1<<((val&(RTCRS0|RTCRS1|RTCRS2|RTCRS3))-1);
rtctime += (int)(RTCCONST * c * (1 << TIMER_SHIFT));
}
else
rtctime = 0x7fffffff;
}
else
{
old = nvrram[nvraddr];
nvrram[nvraddr]=val;
if (nvraddr==RTCREGB)
{
if (((old ^ val) & RTCSET) && (val & RTCSET))
{
nvrram[RTCREGA] &= ~RTCUIP; /* This has to be done according to the datasheet. */
nvrram[RTCREGB] &= ~RTCUIE; /* This also has to happen per the specification. */
}
}
if ((nvraddr < RTCREGA) || (nvraddr == RTCCENTURY))
{
if ((nvraddr != 1) || (nvraddr != 3) || (nvraddr != 5))
{
if ((old != val) && !enable_sync)
{
time_update(nvrram, nvraddr);
nvr_dosave = 1;
}
}
}
}
}
else nvraddr=val&nvrmask;
}
@@ -338,20 +168,14 @@ uint8_t readnvr(uint16_t addr, void *priv)
// printf("Read NVR %03X %02X %02X %04X:%04X\n",addr,nvraddr,nvrram[nvraddr],cs>>4,pc);
if (addr&1)
{
if (nvraddr<=0xA) getnvrtime();
if (nvraddr==0xD) nvrram[0xD]|=0x80;
if (nvraddr==0xA)
{
temp=nvrram[0xA];
nvrram[0xA]&=~0x80;
return temp;
}
if (nvraddr==0xC)
if (nvraddr==RTCREGA) return ((nvrram[RTCREGA] & 0x7F) | nvr_update_status);
if (nvraddr==RTCREGD) nvrram[RTCREGD]|=RTCVRT;
if (nvraddr==RTCREGC)
{
if (AMSTRAD) picintc(2);
else picintc(0x100);
temp=nvrram[0xC];
nvrram[0xC]=0;
temp=nvrram[RTCREGC];
nvrram[RTCREGC]=0; /* All flags in register C are unused (always 0) or cleared on read */
return temp;
}
// if (AMIBIOS && nvraddr==0x36) return 0;
@@ -379,7 +203,7 @@ void loadnvr()
case ROM_IBMPS1_2121: f = romfopen("nvr/ibmps1_2121.nvr", "rb"); nvrmask = 127; break;
case ROM_CMDPC30: f = romfopen("nvr/cmdpc30.nvr", "rb"); nvrmask = 127; break;
case ROM_AMI286: f = romfopen("nvr/ami286.nvr", "rb"); nvrmask = 127; break;
case ROM_AWARD286: f = romfopen("nvr/award286.nvr", "rb"); nvrmask = 127; break;
case ROM_AWARD286: f = romfopen("nvr/award286.nvr", "rb"); nvrmask = 127; break;
case ROM_DELL200: f = romfopen("nvr/dell200.nvr", "rb"); nvrmask = 127; break;
case ROM_IBMAT386: f = romfopen("nvr/at386.nvr", "rb"); nvrmask = 127; break;
case ROM_DESKPRO_386: f = romfopen("nvr/deskpro386.nvr", "rb"); break;
@@ -413,19 +237,22 @@ void loadnvr()
if (!f)
{
memset(nvrram,0xFF,128);
if (!enable_sync)
{
nvrram[RTCSECONDS] = nvrram[RTCMINUTES] = nvrram[RTCHOURS] = 0;
nvrram[RTCDOM] = nvrram[RTCMONTH] = 1;
nvrram[RTCYEAR] = BCD(80);
nvrram[RTCCENTURY] = BCD(19);
nvrram[RTCREGB]=RTC2412;
}
return;
}
fread(nvrram,128,1,f);
if (!(feof(f)))
fread(internal_time,6,4,f);
else
{
if (!enable_sync) get_registers();
}
if (!enable_sync) time_update(nvrram, 0xFF); /* Update the internal clock state based on the NVR registers. */
fclose(f);
nvrram[0xA]=6;
nvrram[0xB]=0;
c=1<<((6&0xF)-1);
nvrram[RTCREGA]=(RTCRS1|RTCRS2);
nvrram[RTCREGB]=RTC2412;
c=1<<((nvrram[RTCREGA]&(RTCRS0|RTCRS1|RTCRS2|RTCRS3))-1);
rtctime += (int)(RTCCONST * c * (1 << TIMER_SHIFT));
}
void savenvr()
@@ -443,7 +270,7 @@ void savenvr()
case ROM_IBMPS1_2121: f = romfopen("nvr/ibmps1_2121.nvr", "wb"); break;
case ROM_CMDPC30: f = romfopen("nvr/cmdpc30.nvr", "wb"); break;
case ROM_AMI286: f = romfopen("nvr/ami286.nvr", "wb"); break;
case ROM_AWARD286: f = romfopen("nvr/award286.nvr", "wb"); break;
case ROM_AWARD286: f = romfopen("nvr/award286.nvr", "wb"); break;
case ROM_DELL200: f = romfopen("nvr/dell200.nvr", "wb"); break;
case ROM_IBMAT386: f = romfopen("nvr/at386.nvr", "wb"); break;
case ROM_DESKPRO_386: f = romfopen("nvr/deskpro386.nvr", "wb"); break;
@@ -474,10 +301,7 @@ void savenvr()
case ROM_KN97: f = romfopen("nvr/kn97.nvr", "wb"); break;
default: return;
}
/* If sync is disabled, save internal clock to registers. */
if (!enable_sync) set_registers();
fwrite(nvrram,128,1,f);
fwrite(internal_time,6,4,f);
fclose(f);
}

View File

@@ -1,11 +1,8 @@
void nvr_init();
extern int nvr_dosave;
extern int enable_sync;
void time_sleep(int count);
extern int nvr_dosave;
void time_get(char *nvrram);
void nvr_add_10sec();
void update_sync();

View File

@@ -675,7 +675,7 @@ void loadconfig(char *fn)
enable_overscan = config_get_int(NULL, "enable_overscan", 0);
enable_flash = config_get_int(NULL, "enable_flash", 1);
enable_sync = config_get_int(NULL, "enable_sync", 0);
enable_sync = config_get_int(NULL, "enable_sync", 1);
mouse_always_serial = config_get_int(NULL, "mouse_always_serial", 0);
window_w = config_get_int(NULL, "window_w", 0);

261
src/rtc.c Normal file
View File

@@ -0,0 +1,261 @@
/* Emulation of:
Dallas Semiconductor DS12C887 Real Time Clock
http://datasheets.maximintegrated.com/en/ds/DS12885-DS12C887A.pdf
http://dev-docs.atariforge.org/files/MC146818A_RTC_1984.pdf
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "nvr.h"
#include "rtc.h"
int enable_sync;
typedef struct
{
int sec;
int min;
int hour;
int mday;
int mon;
int year;
}
internal_clock_t;
internal_clock_t internal_clock;
/* When the RTC was last updated */
time_t rtc_set_time = 0;
/* Table for days in each month */
int rtc_days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* Called to determine whether the year is leap or not */
static int rtc_is_leap(int org_year)
{
if (org_year % 400 == 0) return 1;
if (org_year % 100 == 0) return 0;
if (org_year % 4 == 0) return 1;
return 0;
}
/* Called to determine the days in the current month */
static int rtc_get_days(int org_month, int org_year)
{
if (org_month != 2)
{
return rtc_days_in_month[org_month];
}
else
{
return rtc_is_leap(org_year) ? 29 : 28;
}
}
/* Called when the internal clock gets updated */
static void rtc_recalc()
{
if (internal_clock.sec == 60)
{
internal_clock.sec = 0;
internal_clock.min++;
}
if (internal_clock.min == 60)
{
internal_clock.min = 0;
internal_clock.hour++;
}
if (internal_clock.hour == 24)
{
internal_clock.hour = 0;
internal_clock.mday++;
}
if (internal_clock.mday == (rtc_get_days(internal_clock.mon, internal_clock.year) + 1))
{
internal_clock.mday = 1;
internal_clock.mon++;
}
if (internal_clock.mon == 13)
{
internal_clock.mon = 1;
internal_clock.year++;
}
nvr_dosave = 1;
}
/* Called when ticking the second */
void rtc_tick()
{
internal_clock.sec++;
rtc_recalc();
}
/* Called when modifying the NVR registers */
void time_update(char *nvrram, int reg)
{
int temp;
switch(reg)
{
case RTCSECONDS:
internal_clock.sec = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCSECONDS] : DCB(nvrram[RTCSECONDS]);
break;
case RTCMINUTES:
internal_clock.min = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMINUTES] : DCB(nvrram[RTCMINUTES]);
break;
case RTCHOURS:
temp = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCHOURS] : DCB(nvrram[RTCHOURS]);
if (nvrram[RTCREGB] & RTC2412)
{
internal_clock.hour = temp;
}
else
{
internal_clock.hour = ((temp & ~RTCAMPM) % 12) + ((temp & RTCAMPM) ? 12 : 0);
}
break;
case RTCDOM:
internal_clock.mday = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCDOM] : DCB(nvrram[RTCDOM]);
break;
case RTCMONTH:
internal_clock.mon = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMONTH] : DCB(nvrram[RTCMONTH]);
break;
case RTCYEAR:
internal_clock.year = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCYEAR] : DCB(nvrram[RTCYEAR]);
internal_clock.year += (nvrram[RTCREGB] & RTCDM) ? 1900 : (DCB(nvrram[RTCCENTURY]) * 100);
break;
case RTCCENTURY:
if (nvrram[RTCREGB] & RTCDM) return;
internal_clock.year %= 100;
internal_clock.year += (DCB(nvrram[RTCCENTURY]) * 100);
break;
case 0xFF: /* Load the entire internal clock state from the NVR. */
internal_clock.sec = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCSECONDS] : DCB(nvrram[RTCSECONDS]);
internal_clock.min = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMINUTES] : DCB(nvrram[RTCMINUTES]);
temp = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCHOURS] : DCB(nvrram[RTCHOURS]);
if (nvrram[RTCREGB] & RTC2412)
{
internal_clock.hour = temp;
}
else
{
internal_clock.hour = ((temp & ~RTCAMPM) % 12) + ((temp & RTCAMPM) ? 12 : 0);
}
internal_clock.mday = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCDOM] : DCB(nvrram[RTCDOM]);
internal_clock.mon = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMONTH] : DCB(nvrram[RTCMONTH]);
internal_clock.year = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCYEAR] : DCB(nvrram[RTCYEAR]);
internal_clock.year += (nvrram[RTCREGB] & RTCDM) ? 1900 : (DCB(nvrram[RTCCENTURY]) * 100);
break;
}
}
/* Called to obtain the current day of the week based on the internal clock */
static int time_week_day()
{
int day_of_month = internal_clock.mday;
int month2 = internal_clock.mon;
int year2 = internal_clock.year % 100;
int century = ((internal_clock.year - year2) / 100) % 4;
int sum = day_of_month + month2 + year2 + century;
/* (Sum mod 7) gives 0 for Saturday, we need it for Sunday, so +6 for Saturday to get 6 and Sunday 0 */
int raw_wd = ((sum + 6) % 7);
return raw_wd;
}
/* Called to get time into the internal clock */
static void time_internal(struct tm **time_var)
{
if (*time_var == NULL) *time_var = (struct tm *) malloc(sizeof(struct tm));
(*time_var)->tm_sec = internal_clock.sec;
(*time_var)->tm_min = internal_clock.min;
(*time_var)->tm_hour = internal_clock.hour;
(*time_var)->tm_wday = time_week_day();
(*time_var)->tm_mday = internal_clock.mday;
(*time_var)->tm_mon = internal_clock.mon - 1;
(*time_var)->tm_year = internal_clock.year - 1900;
}
/* Periodic RTC update function
See also: nvr_onesec() in nvr.c
*/
void time_get(char *nvrram)
{
time_t cur_time;
struct tm* cur_time_tm;
int dow, mon, year;
if (enable_sync)
{
cur_time = time(NULL);
/* Mingw doesn't support localtime_r */
#if __MINGW32__
cur_time_tm = localtime(&cur_time);
#else
#if __MINGW64__
cur_time_tm = localtime(&cur_time);
#else
localtime_r(&cur_time, &cur_time_tm);
#endif
#endif
}
else
{
time_internal(&cur_time_tm);
}
if (nvrram[RTCREGB] & RTCDM)
{
nvrram[RTCSECONDS] = cur_time_tm->tm_sec;
nvrram[RTCMINUTES] = cur_time_tm->tm_min;
nvrram[RTCDOW] = cur_time_tm->tm_wday + 1;
nvrram[RTCDOM] = cur_time_tm->tm_mday;
nvrram[RTCMONTH] = cur_time_tm->tm_mon + 1;
nvrram[RTCYEAR] = cur_time_tm->tm_year % 100;
if (nvrram[RTCREGB] & RTC2412)
{
nvrram[RTCHOURS] = cur_time_tm->tm_hour;
}
else
{
nvrram[RTCHOURS] = (cur_time_tm->tm_hour % 12) ? (cur_time_tm->tm_hour % 12) : 12;
if (cur_time_tm->tm_hour > 11)
{
nvrram[RTCHOURS] |= RTCAMPM;
}
}
}
else
{
nvrram[RTCSECONDS] = BCD(cur_time_tm->tm_sec);
nvrram[RTCMINUTES] = BCD(cur_time_tm->tm_min);
nvrram[RTCDOW] = BCD(cur_time_tm->tm_wday + 1);
nvrram[RTCDOM] = BCD(cur_time_tm->tm_mday);
nvrram[RTCMONTH] = BCD(cur_time_tm->tm_mon + 1);
nvrram[RTCYEAR] = BCD(cur_time_tm->tm_year % 100);
if (nvrram[RTCREGB] & RTC2412)
{
nvrram[RTCHOURS] = BCD(cur_time_tm->tm_hour);
}
else
{
nvrram[RTCHOURS] = (cur_time_tm->tm_hour % 12) ? BCD(cur_time_tm->tm_hour % 12) : BCD(12);
if (cur_time_tm->tm_hour > 11)
{
nvrram[RTCHOURS] |= RTCAMPM;
}
}
}
}

186
src/rtc.h Normal file
View File

@@ -0,0 +1,186 @@
#define BCD(X) (((X) % 10) | (((X) / 10) << 4))
#define DCB(X) ((((X) & 0xF0) >> 4) * 10 + ((X) & 0x0F))
enum RTCADDR
{
RTCSECONDS,
RTCALARMSECONDS,
RTCMINUTES,
RTCALARMMINUTES,
RTCHOURS,
RTCALARMHOURS,
RTCDOW,
RTCDOM,
RTCMONTH,
RTCYEAR,
RTCREGA,
RTCREGB,
RTCREGC,
RTCREGD
};
/* The century register at location 32h is a BCD register designed to automatically load the BCD value 20 as the year register changes from 99 to 00.
The MSB of this register is not affected when the load of 20 occurs, and remains at the value written by the user. */
#define RTCCENTURY 0x32
/* When the 12-hour format is selected, the higher-order bit of the hours byte represents PM when it is logic 1. */
#define RTCAMPM 0b10000000
/* Register A bitflags */
enum RTCRABITS
{
/* Rate Selector (RS0)
These four rate-selection bits select one of the 13 taps on the 15-stage divider or disable the divider output.
The tap selected can be used to generate an output square wave (SQW pin) and/or a periodic interrupt.
The user can do one of the following:
- Enable the interrupt with the PIE bit;
- Enable the SQW output pin with the SQWE bit;
- Enable both at the same time and the same rate; or
- Enable neither.
Table 3 lists the periodic interrupt rates and the square wave frequencies that can be chosen with the RS bits.
These four read/write bits are not affected by !RESET. */
RTCRS0 = 0b1,
RTCRS1 = 0b10, /*!<RS1*/
RTCRS2 = 0b100, /*!<RS2*/
RTCRS3 = 0b1000, /*!<RS3*/
/* DV0
These three bits are used to turn the oscillator on or off and to reset the countdown chain.
A pattern of 010 is the only combination of bits that turn the oscillator on and allow the RTC to keep time.
A pattern of 11x enables the oscillator but holds the countdown chain in reset.
The next update occurs at 500ms after a pattern of 010 is written to DV0, DV1, and DV2. */
RTCDV0 = 0b10000,
RTCDV1 = 0b100000, /*!<DV1*/
RTCDV2 = 0b1000000, /*!<DV2*/
/* Update-In-Progress (UIP)
This bit is a status flag that can be monitored. When the UIP bit is a 1, the update transfer occurs soon.
When UIP is a 0, the update transfer does not occur for at least 244us.
The time, calendar, and alarm information in RAM is fully available for access when the UIP bit is 0.
The UIP bit is read-only and is not affected by !RESET.
Writing the SET bit in Register B to a 1 inhibits any update transfer and clears the UIP status bit. */
RTCUIP = 0b10000000
};
/* Register B bitflags */
enum RTCRBBITS
{
/* Daylight Saving Enable (DSE)
This bit is a read/write bit that enables two daylight saving adjustments when DSE is set to 1.
On the first Sunday in April (or the last Sunday in April in the MC146818A), the time increments from 1:59:59 AM to 3:00:00 AM.
On the last Sunday in October when the time first reaches 1:59:59 AM, it changes to 1:00:00 AM.
When DSE is enabled, the internal logic test for the first/last Sunday condition at midnight.
If the DSE bit is not set when the test occurs, the daylight saving function does not operate correctly.
These adjustments do not occur when the DSE bit is 0. This bit is not affected by internal functions or !RESET. */
RTCDSE = 0b1,
/* 24/12
The 24/12 control bit establishes the format of the hours byte. A 1 indicates the 24-hour mode and a 0 indicates the 12-hour mode.
This bit is read/write and is not affected by internal functions or !RESET. */
RTC2412 = 0b10,
/* Data Mode (DM)
This bit indicates whether time and calendar information is in binary or BCD format.
The DM bit is set by the program to the appropriate format and can be read as required.
This bit is not modified by internal functions or !RESET. A 1 in DM signifies binary data, while a 0 in DM specifies BCD data. */
RTCDM = 0b100,
/* Square-Wave Enable (SQWE)
When this bit is set to 1, a square-wave signal at the frequency set by the rate-selection bits RS3-RS0 is driven out on the SQW pin.
When the SQWE bit is set to 0, the SQW pin is held low. SQWE is a read/write bit and is cleared by !RESET.
SQWE is low if disabled, and is high impedance when VCC is below VPF. SQWE is cleared to 0 on !RESET. */
RTCSQWE = 0b1000,
/* Update-Ended Interrupt Enable (UIE)
This bit is a read/write bit that enables the update-end flag (UF) bit in Register C to assert !IRQ.
The !RESET pin going low or the SET bit going high clears the UIE bit.
The internal functions of the device do not affect the UIE bit, but is cleared to 0 on !RESET. */
RTCUIE = 0b10000,
/* Alarm Interrupt Enable (AIE)
This bit is a read/write bit that, when set to 1, permits the alarm flag (AF) bit in Register C to assert !IRQ.
An alarm interrupt occurs for each second that the three time bytes equal the three alarm bytes, including a don't-care alarm code of binary 11XXXXXX.
The AF bit does not initiate the !IRQ signal when the AIE bit is set to 0.
The internal functions of the device do not affect the AIE bit, but is cleared to 0 on !RESET. */
RTCAIE = 0b100000,
/* Periodic Interrupt Enable (PIE)
The PIE bit is a read/write bit that allows the periodic interrupt flag (PF) bit in Register C to drive the !IRQ pin low.
When the PIE bit is set to 1, periodic interrupts are generated by driving the !IRQ pin low at a rate specified by the RS3-RS0 bits of Register A.
A 0 in the PIE bit blocks the !IRQ output from being driven by a periodic interrupt, but the PF bit is still set at the periodic rate.
PIE is not modified by any internal device functions, but is cleared to 0 on !RESET. */
RTCPIE = 0b1000000,
/* SET
When the SET bit is 0, the update transfer functions normally by advancing the counts once per second.
When the SET bit is written to 1, any update transfer is inhibited, and the program can initialize the time and calendar bytes without an update
occurring in the midst of initializing. Read cycles can be executed in a similar manner. SET is a read/write bit and is not affected by !RESET or
internal functions of the device. */
RTCSET = 0b10000000
};
/* Register C bitflags */
enum RTCRCBITS
{
/* Unused
These bits are unused in Register C. These bits always read 0 and cannot be written. */
RTCRC0 = 0b1,
RTCRC1 = 0b10, /*!<Unused*/
RTCRC2 = 0b100, /*!<Unused*/
RTCRC3 = 0b1000, /*!<Unused*/
/* Update-Ended Interrupt Flag (UF)
This bit is set after each update cycle. When the UIE bit is set to 1, the 1 in UF causes the IRQF bit to be a 1, which asserts the !IRQ pin.
This bit can be cleared by reading Register C or with a !RESET. */
RTCUF = 0b10000,
/* Alarm Interrupt Flag (AF)
A 1 in the AF bit indicates that the current time has matched the alarm time.
If the AIE bit is also 1, the !IRQ pin goes low and a 1 appears in the IRQF bit. This bit can be cleared by reading Register C or with a !RESET. */
RTCAF = 0b100000,
/* Periodic Interrupt Flag (PF)
This bit is read-only and is set to 1 when an edge is detected on the selected tap of the divider chain.
The RS3 through RS0 bits establish the periodic rate. PF is set to 1 independent of the state of the PIE bit.
When both PF and PIE are 1s, the !IRQ signal is active and sets the IRQF bit. This bit can be cleared by reading Register C or with a !RESET. */
RTCPF = 0b1000000,
/* Interrupt Request Flag (IRQF)
The interrupt request flag (IRQF) is set to a 1 when one or more of the following are true:
- PF == PIE == 1
- AF == AIE == 1
- UF == UIE == 1
Any time the IRQF bit is a 1, the !IRQ pin is driven low.
All flag bits are cleared after Register C is read by the program or when the !RESET pin is low. */
RTCIRQF = 0b10000000
};
/* Register D bitflags */
enum RTCRDBITS
{
/* Unused
The remaining bits of Register D are not usable. They cannot be written and they always read 0. */
RTCRD0 = 0b1,
RTCRD1 = 0b10, /*!<Unused*/
RTCRD2 = 0b100, /*!<Unused*/
RTCRD3 = 0b1000, /*!<Unused*/
RTCRD4 = 0b10000, /*!<Unused*/
RTCRD5 = 0b100000, /*!<Unused*/
RTCRD6 = 0b1000000, /*!<Unused*/
/* Valid RAM and Time (VRT)
This bit indicates the condition of the battery connected to the VBAT pin. This bit is not writeable and should always be 1 when read.
If a 0 is ever present, an exhausted internal lithium energy source is indicated and both the contents of the RTC data and RAM data are questionable.
This bit is unaffected by !RESET. */
RTCVRT = 0b10000000
};
void rtc_tick();
void time_update(char *nvrram, int reg);
void time_get(char *nvrram);

View File

@@ -339,7 +339,6 @@ static BOOL CALLBACK config_dlgproc(HWND hdlg, UINT message, WPARAM wParam, LPAR
h = GetDlgItem(hdlg, IDC_CHECKSYNC);
enable_sync = SendMessage(h, BM_GETCHECK, 0, 0);
update_sync();
h = GetDlgItem(hdlg, IDC_CHECKSERIAL);
temp_always_serial = SendMessage(h, BM_GETCHECK, 0, 0);

View File

@@ -1,48 +0,0 @@
#include <windows.h>
#include "ibm.h"
#include "nvr.h"
void time_sleep(int count)
{
Sleep(count);
}
void time_get(char *nvrram)
{
SYSTEMTIME systemtime;
int c, d;
uint8_t baknvr[10];
memcpy(baknvr,nvrram,10);
GetLocalTime(&systemtime);
d = systemtime.wSecond % 10;
c = systemtime.wSecond / 10;
nvrram[0] = d | (c << 4);
d = systemtime.wMinute % 10;
c = systemtime.wMinute / 10;
nvrram[2] = d | (c << 4);
d = systemtime.wHour % 10;
c = systemtime.wHour / 10;
nvrram[4] = d | (c << 4);
d = systemtime.wDayOfWeek % 10;
c = systemtime.wDayOfWeek / 10;
nvrram[6] = d | (c << 4);
d = systemtime.wDay % 10;
c = systemtime.wDay / 10;
nvrram[7] = d | (c << 4);
d = systemtime.wMonth % 10;
c = systemtime.wMonth / 10;
nvrram[8] = d | (c << 4);
d = systemtime.wYear % 10;
c = (systemtime.wYear / 10) % 10;
nvrram[9] = d | (c << 4);
if (baknvr[0] != nvrram[0] ||
baknvr[2] != nvrram[2] ||
baknvr[4] != nvrram[4] ||
baknvr[6] != nvrram[6] ||
baknvr[7] != nvrram[7] ||
baknvr[8] != nvrram[8] ||
baknvr[9] != nvrram[9])
nvrram[0xA]|=0x80;
}