VS1010 Real-Time Clock

Designing hardware and software for systems that use the VS1010 MP3 Audio DSP Microcontroller.
Post Reply
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

VS1010 Real-Time Clock

Post by Kalle » Tue 2017-06-27 13:21

Rtc.c, and its header file rtc.h, include functions for using the VS1010 Real Time Clock. See VS1010 datasheet chapter 9.17 for details.
The functions are mostly based on the RTC library of VS1005, but some modifications have been made. The major difference between VS1010 and VS1005 RTCs is that the VS1010 doesn't have a RAM, unlike the VS1005. This is somewhat remedied by using a 32-bit shifter register in the VS1010. If the RTC battery has been attached, the shifter register can be used to store a small amount of data while the device is otherwise powered down. Naturally the actual RTC counter is also functional with just the battery. The RTC also functions with only the 5V power. However, note that attaching or removing the battery can often cause the counter to lose it's value. Ideally the battery should be kept connected at all times.

This thread also includes a few programs which use the RTC. If you wish to see more detailed explanations for anything, take a look at the comments in the source code files.

The RTC has three interface registers. Two of them are 16-bit data registers, RTC_LOW and RTC_HIGH. The third is a 5-bit control and status register RTC_CF. The addresses, control bits and instructions have been defined in vs1010bRom.h. The first function in rtc.c is a small helper function, which is used to control the RTC_CF register.

Code: Select all

void StartAndWaitRtcReady(register u_int16 rtcCfBits) {
	PERIP(RTC_CF) = rtcCfBits;
	while (PERIP(RTC_CF));
}
The function can be used to set a control bit to 1, and the RTC will perform the approriate action. After the RTC is done, it will set the control bit back to 0. Until that happens, the program will stay in the while loop.

Code: Select all

void WriteShifter(u_int32 data) {

	PERIP(RTC_HIGH) = 0;
 	StartAndWaitRtcReady(RTC_CF_IBUSY);

	PERIP(RTC_HIGH) = (u_int16)(data >> 16);
	PERIP(RTC_LOW) = (u_int16)data;
	StartAndWaitRtcReady(RTC_CF_DBUSY);
}
The next function is WriteShifter which essentially has two parts. First is loading an empty instruction with the instruction cycle flag. This will prevent any already running instructions from overriding the data which is being written. The second part is the actual shifter input. The data is first set to the two interface data registers, and then moved from there to the shifter register by setting the data cycle flag.

Code: Select all

u_int32 ReadShifter() {

	u_int32 shifter;

	StartAndWaitRtcReady(RTC_CF_RDBUSY);
	
	shifter = (((u_int32)PERIP(RTC_HIGH)) << 16) | (u_int32)PERIP(RTC_LOW); 
	WriteShifter(shifter);
	
	return shifter;
}
To read the shifter value, the read cycle flag is set to 1. This will move the shifter data to the interface registers, where it can be read. Since this is a shifter register, the data must be written back to it with the WriteShifter() function.

From here on, every function will begin with reading the shifter's value, and end with writing it back. Otherwise running the other operations would cause the data to be lost.

Code: Select all

unsigned long GetRtc(void) {

	u_int32 shifter = ReadShifter();
	u_int32 rtc;
	
	PERIP(RTC_HIGH) = RTC_I_READRTC;
	StartAndWaitRtcReady(RTC_CF_IBUSY);
 	
	StartAndWaitRtcReady(RTC_CF_RDBUSY);
  	rtc = (((u_int32)PERIP(RTC_HIGH)) << 16) | (u_int32)PERIP(RTC_LOW);

	WriteShifter(shifter);
  	
  	return rtc;	
}
Reading the RTC counter's value is done with the RTC_I_READRTC instruction. Setting the instruction cycle flag will move the instruction code to the instruction register and execute it. The result is then moved from the buffer to the interface data registers by setting the read cycle flag.

Code: Select all

u_int32 SetRtc(register u_int32 t) {

	u_int32 shifter = ReadShifter();

  	PERIP(RTC_HIGH) = RTC_I_LOADRTC;
	StartAndWaitRtcReady(RTC_CF_IBUSY);
	
	PERIP(RTC_HIGH) = (u_int16)(t >> 16);
	PERIP(RTC_LOW) = (u_int16)t;
	StartAndWaitRtcReady(RTC_CF_DBUSY);
 
	PERIP(RTC_CF) = RTC_CF_EXEC;
	printf("Delay starts \n");
	DelayL(10000000);
	printf("Delay ends \n");
	PERIP(RTC_CF) = 0;

	WriteShifter(shifter);

	return t;
}
Setting the RTC counter's value also starts with loading an instruction to the instruction register, but the RTC_I_LOADRTC won't execute yet. Instead the input data is loaded to the buffer by setting the read cycle flag. The LOADRTC is then executed by setting the RTC_CF_EXEC bit of the control register. This action is only required with the LOADRTC and RESET instructions. The EXEC flag also needs to stay high for at least one second, after which it has to be resetted manually.

Code: Select all

u_int16 GetRtc128(void) {

	u_int32 shifter = ReadShifter();
  	u_int32 rtc128;

	PERIP(RTC_HIGH) = RTC_I_DIV128;
	StartAndWaitRtcReady(RTC_CF_IBUSY);
	
	StartAndWaitRtcReady(RTC_CF_RDBUSY);
	rtc128 = (PERIP(RTC_HIGH) >> 9) ^ 64;
	
	WriteShifter(shifter);

	return rtc128;
}
The GetRtc128() function is used for getting the value of the 8-bit divider of the RTC. The function is practically identical to GetRtc(). The instruction given is RTC_I_DIV128 instead of the RTC read, and the result is modified before it's returned.


The rtc.h and rtc.c files are available for download below. There are also four small additional programs, which are each used for calling one of the rtc functions. WShifter and RShifter are for writing and reading shifter values, and RtcSet and RtcGet are for writing and reading the RTC counter values. The dlx.zip package includes all the .dlx files.
Attachments
dlx.zip
.dlx files for WShifter, RShifter, RtcSet, RtcGet, RtcRead, SetDate and Date.
(10.85 KiB) Downloaded 13 times
WShifter.zip
VS1010 WShifter - VSIDE solution
(7.69 KiB) Downloaded 12 times
RtcSet.zip
VS1010 RtcSet - VSIDE solution
(7.78 KiB) Downloaded 13 times
RtcGet.zip
VS1010 RtcGet - VSIDE solution
(7.32 KiB) Downloaded 12 times
rtc.zip
VS1010 rtc.c and rtc.h
(2.01 KiB) Downloaded 12 times
RShifter.zip
VS1010 RShifter - VSIDE solution
(7.49 KiB) Downloaded 13 times
Last edited by Kalle on Fri 2017-06-30 12:57, edited 7 times in total.

Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

VS1010 RtcRead

Post by Kalle » Tue 2017-06-27 16:15

This program has been ported from the VS1005. The code has also been shortened by removing everything unused.

The purpose of RtcRead is to update the current time of the RTC counter to the VSOS currentTime struct. This requires converting the pure second value of the counter to the human-readable format. On VSOS 3.24 and newer RtcRead is automatically called after writing a file. Note that if you examine any written files in Windows, the last modification time can be off by an hour, probably due to the way Windows handles daylight savings time.

In addition to the RTC, the program also uses VS1005's time library (time.c and time.h).

The program is presented here in two parts. First part has the definitions and some error checking, and the second part has the main functionality.

Code: Select all

#include <vo_stdio.h>
#include <string.h>
#include "rtc.h"
#include "time.h"

#define leap(y) (((y) & 3) == 0)

int __mem_y monLen[2][12] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};

ioresult main(void) {

	time_t t = GetRtc(), nextT;

	memset(&currentTime, 0, sizeof(currentTime));

	if (t < 0) {	
		currentTime.tm_year = 99;
		currentTime.tm_mon = 12-1;
		currentTime.tm_mday = 31;
		currentTime.tm_hour = 12;
		return;
	}
The program gets the current counter value from the RTC with the GetRtc() function. The currentTime struct is effectively resetted by filling it with zeros. If the RTC counter value is less than zero, which is an error, the currentTime will be set to a predefined value.

Code: Select all

	currentTime.tm_year = 100;
	while ((nextT = t - (365+leap(currentTime.tm_year+1900))*86400) >= 0) {
		currentTime.tm_year++;
		t = nextT;
	}
	
	while ((nextT = t - monLen[leap(currentTime.tm_year+1900)][currentTime.tm_mon]*86400) >= 0) {
		currentTime.tm_mon++;
		t = nextT;
	}

	currentTime.tm_mday = (int)(t/86400);
	t -= currentTime.tm_mday*86400;
	currentTime.tm_hour = (int)(t/3600);
	t -= currentTime.tm_hour*3600;
	currentTime.tm_min = (int)t/60;
	currentTime.tm_sec = (int)t - currentTime.tm_min*60;

	currentTime.tm_mday++;

	return S_OK;
}
The other half of the main() has the main functionality of the program. The program starts with decreasing as many full years as possible from the counter value (86400 is amount of seconds in a day, multiplied with 365 or 366), while incrementing the tm_year of the currentTime. Leap adds an extra day if the year is a leap year. Same is done with months, except the length of the months comes from the monLen struct. If the year is a leap year, the second row (index 1) of the struct is used. The program follows a similar principle for the smaller time units, until seconds are reached. At the end tm_mday is incremented by one, since the numbering of days starts from 1. Months use a 0-11 range internally.

The VSIDE solution is available for download below.
Attachments
RtcRead10.zip
VS1010 RtcRead - VSIDE solution
(13.33 KiB) Downloaded 12 times
Last edited by Kalle on Fri 2017-06-30 12:50, edited 1 time in total.

Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

VS1010 SetDate

Post by Kalle » Wed 2017-06-28 15:31

This program has been ported from the VS1005. The code has also been shortened by removing everything unused.

SetDate can be used to set the date and/or time of the RTC. In addition to rtc and time, mktime.c and the ParamSpl program are required.

SetDate is presented here in three parts.

Code: Select all

#include <vo_stdio.h>
#include <volink.h>
#include <string.h>
#include <vs1010bRom.h>
#include "time.h"
#include "rtc.h"

int __mem_y monLen[2][12] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	  {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};


ioresult main(char *parameters) {
	
	int nParam = RunLib("ParamSpl",parameters);
	char *p = parameters;
	int i;

	int set = 0;
	time_t tim = time(NULL);
	
	struct tm *tm = localtime(&tim);
	
	ioresult res = S_OK;
	
	if (nParam = -31234) {
		printf("ParamSpl not found\n");
	}

RunLib is used to run the ParamSpl program, which will splits the parameters and returns their amount to nParam. If ParamSpl isn't found, RunLib will return the value -31234. If this happens, neither the for loop or the if statement after it will execute, due to the values of the variables. Only the error message will be printed.

The set variable is used as a flag to see if properly formatted time or date parameters were given. Time() is used to return the current RTC value to the tim variable. This is then converted to human readable form with localtime() and stored to the tm struct. The struct is used as a temporary storage for the given date and time values before they are set to the RTC. The ioresult is modified if any errors are encountered.

Code: Select all

	for (i=0; i<nParam; i++) {
	
		if (!strcmp(p, "-h")) {
			printf("Usage: SetDate [YYYY-MM-DD|YY-MM-DD|HH:MM:SS] [-h]\n"
				"YYYY-MM-DD\tSet date, e.g. 2015-09-18\n"
				"YY-MM-DD\tSet date, e.g. 15-09-18\n"
				"HH:MM:SS\tSet time, e.g. 12:34:56\n"
				"HH:MM\t\tSet time, e.g. 12:34\n"
				"-h\t\tShow this help\n");
			return S_OK;
		
		} else {
			
			// YYYY-MM-DD
			if (strlen(p) == 10 && p[4] == '-' && p[7] == '-')  {
				tm->tm_year = atoi(p+0)-1900;
				tm->tm_mon = atoi(p+5)-1;
				tm->tm_mday = atoi(p+8);
				set |= 1;
				
			// YY-MM-DD
			} else if (strlen(p) == 8 && p[2] == '-' && p[5] == '-')  {
				tm->tm_year = atoi(p+0)+100;
				tm->tm_mon = atoi(p+3)-1;
				tm->tm_mday = atoi(p+6);
				set |= 1;
				
			// HH:MM:SS
			} else if (strlen(p) == 8 && p[2] == ':' && p[5] == ':')  {
				tm->tm_hour = atoi(p+0);
				tm->tm_min = atoi(p+3);
				tm->tm_sec = atoi(p+6);
				set |= 2;
				
			// HH:MM	
			} else if (strlen(p) == 5 && p[2] == ':')  {
				tm->tm_hour = atoi(p+0);	
				tm->tm_min = atoi(p+3);
				tm->tm_sec = 0;
				set |= 2;
				
			} else {
				printf("E: Malformed parameter \"%s\"\n", p);
				res = S_ERROR;
				goto finally;	
			}
		}
		p += strlen(p)+1;
	}
The for loop iterates through the parameters. If "-h" was given, the help will be printed and the program will terminate. In this case no changes are done to the RTC counter, even if date or time parameters were given.

SetDate accepts two different formats for both time (HH:MM:SS and HH:MM) and date (YYYY-MM-DD and YY-MM-DD). Multiple parameters of the same type can be given, in which case the last one will be applied to RTC (e.g. "setdate 2017-06-28 2015-01-01 12:00 15:15:15" will set the RTC to 2015-01-01 15:15:15).

Giving any invalid parameters will stop the execution without making any changes.

The set variable is essentially two flags combined to one variable. The first bit (LSB) is 1 if a properly formatted date parameter was given, and the second is 1 if a valid time parameter was given.

Code: Select all

	if (set) {
		
		u_int32 t = (u_int32)mktime(tm);
		
		if (tim < 0 && set < 3) {
			printf("E: RTC not set. You need to set both date and time!\n");
			res = S_ERROR;
		
		} else {
			SetRtc(t);
 	   }
	}

	finally:
		return res;
}
The last if statement does the actual changes to the RTC. If the set variable is zero, no valid parameters were given and program will exit. If it's non-zero, the mktime() function converts the tm struct's value to seconds. If the RTC counter (tim variable) has a negative value (which means that time and date were not previously set), the program requires both date and time as parameters. If there are no errors, SetRtc() will change the RTC to the new value.

The VSIDE solutions for SetDate and ParamSpl are available for download below.
Attachments
SetDate.zip
VS1010 SetDate - VSIDE solution
(17.07 KiB) Downloaded 12 times
ParamSpl.zip
VS1010 ParamSpl - VSIDE solution
(5.7 KiB) Downloaded 14 times

Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

VS1010 Date

Post by Kalle » Fri 2017-06-30 12:34

This program has been ported from the VS1005. The code has been shortened by removing everything unused. Some incompatible features have also been removed.

Date is used to print the current RTC counter time in a human-readable form. Parameters are optional, but they allow use of different printing formats. ParamSpl is required for this functionality. Without it, it's still possible to print in the default format (YYYY-MM-DD HH:MM:SS).

The Date program is presented here in two parts.

Code: Select all

#include <vo_stdio.h>
#include <string.h>
#include <vs1010bRom.h>
#include <vs1010b.h>
#include "time.h"
#include "rtc.h"


int __mem_y monLen[2][12] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};


ioresult main(char *parameters) {

	int nParam = RunLib("ParamSpl",parameters);
	char *p = parameters;
	int i;

	int autoPrint = 1;

	static char s[256];
	
	time_t tim = time(NULL);
	if (tim == TIME_NOT_SET) {
		printf("ERROR! RTC not set. Please use VSOS Shell program SetDate\n"
			"or C function SetRtc() (e.g. SetRtc(mktime(tm))) to set RTC!\n");
		return EXIT_FAILURE;
	}
	
	if (parameters != 0 && nParam == -31234) {
		printf("ParamSpl not found\n");
	}
Similar to SetDate, RunLib tries to call ParamSpl. If some parameters were given but ParamSpl isn't found, an error will be printed. The autoPrint variable is used as a flag to see if printing was done according to given parameters. If not, the default format will be printed. The s struct is used as storage for the string which is going to be printed. Time() is again used to get the RTC value, and if the result is invalid, an error is printed.

Code: Select all

	for (i=0; i<nParam; i++) {

		if (!strcmp(p, "-h")) {
			printf("Usage: Date [formatString] [-h]\n"
				"-h\tShow this help\n\n"
				"FormatString:\n"
				"%%b monName    %%b monthName     %%c date&time  %%d dayOfMonth\n"
				"%%H hour-24    %%h hour-12       %%j dayOfYear  %%m month\n"
				"%%M minute     %%p AM/PM         %%S second     %%x date\n"
				"%%X time       %%y yr            %%Y year       %%%% %%\n");
			return S_OK;
			
		} else {
			strftime(s, 256, p, localtime(&tim));
			puts(s);
			autoPrint = 0;
		}
		p += strlen(p)+1;
	}	

	if (autoPrint) {
 	   strftime(s, 256, "%c", localtime(&tim));
 	   puts(s);	
	}	

	return S_OK;
}
The for loop goes through the parameters. Similar to SetDate, if the "-h" parameter is given the program will terminate. However, any other parameters before it will be printed.

The actual formatting is done by the strftime() function, which is quite flexible. Localtime() also used again to convert the RTC value to the struct form. puts() is used for printing in case there are any formatting characters in the s struct, using printf() could lead to unexpected results with them. If the printing is done with parameters, autoPrint is set to 0. If no parameters were given, the last if statement will be executed, and the "%c" parameter (YYYY-MM-DD HH:MM:SS format) is used for printing.



The VSIDE solution for Date is available below. ParamSpl is required for full functionality.
Attachments
Date10.zip
VS1010 Date - VSIDE solution
(15.47 KiB) Downloaded 11 times

Post Reply

Who is online

Users browsing this forum: No registered users