Controlling VS23S010 with a VS1010

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

Controlling VS23S010 with a VS1010

Post by Kalle » Fri 2017-09-08 13:49

The ntsc10b is a VS1010B specific program, which can be used to control a VS23S010 through SPI. With it, the VS23 can generate the pictured test image. The program is originally based on a VS1005 program ntsctest, which has been cut down to only include code that is essential for the test image creation.
The Arduino and MSP430 versions are modifications of this program. The source codes aren't significantly different, with the exception of the SPI operations.
The program requires an NTSC compatible crystal (3.579545 MHz) on the board.

IMG_20170908_131846.jpg
IMG_20170908_131846.jpg (2.05 MiB) Viewed 118 times

Instructions for executing programs on the VS1010 Dev Board can be found in the Quick Start Guide.

The VSIDE solution and the ntsc10b.dlx are available for download below. If you need for the source code, you can download only the .dlx.
Attachments
ntsc10b.dlx
ntsc10b.dlx
(4.9 KiB) Downloaded 9 times
ntsc10b.zip
ntsc10b VSIDE solution and .dlx
(21.07 KiB) Downloaded 11 times
Last edited by Kalle on Thu 2017-11-02 12:47, edited 2 times in total.

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

Ntsc10b explained

Post by Kalle » Mon 2017-10-09 13:03

This comment has a more in-depth explanation of the ntsc10b.

There are also other NTSC programs for the VS23S010 Breakout Board, which can be controlled either with an Arduino, or the on-board TI MSP430G2432. This comment can be used as a guide for those as well, since they are modifications which are based on this VS1010B program. There are a few differences between them, mostly in the SPI functions. Also, data types were changed with #define macros, and Arduino has it's own method for printing to the PC serial connection.
No simple solution was found for printing straight to PC with the MSP430. That program does have a debug-function, which can be used to output characters to the Breakout Board's pins P1.0 (data) and P1.3 (clock). These outputs can be read with a logic analyzer.
See the more detailed descriptions for:
- the compatibility versions for Arduino and MSP430, or only the MSP430
- the Arduino version
- the MSP430 version



The program consists of the files main.c, ntsc.h, ntsc.c, spisimple.h, spisimple.c and vs23s010.c.

In this version of the program, main.c has only one purpose, which is to call the SpiRamVideoInit-function of the ntsc.c. SpiRamVideoInit is where most of the functionality of the program is. Ntsc.c also has a few small helper functions, as well FilledRectangle which is used for writing the pixel values of the test image.

The header file ntsc.h has a large collection of defined constants which include various addresses, data values, and VS23 SPI opcodes. They have been roughly divided into a few groups, but the file can still be a quite difficult to read.

The spisimple files are for using the SPI. They have the initialization function, as well two functions for the actual SPI communication, one for bytes and one for 16-bit words. Since this is SPI, the data is transmitted and received at the same time, and reading and writing is done with the same functions.
The Arduino and MSP430 version have a completely different executions for these, since the SPI's functionality depends on the hardware. The functions' intefaces are still the same.

The last source code file vs23s010.c has no separate header file, instead its function prototypes can be found in ntsc.h. The vs23s010.c has two functions: SpiRW, which is used for majority of the SPI operations, and SpiRamInit, which is the first function that SpiRamVideoInit calls.

SpiRamInit will further call the SpiInit function of SpiSimple. SpiInit, as the name suggests, is the SPI initialization function. For the VS1010 it sets a selection of SPI config register values, as well as appropriate modes for the different pins that are used.

Code: Select all

// Master mode (bit 5)
// Data length (bits 1-4), used length is the register value + 1. 
// (0111 is 7, with + 1 is 8)
// Frame sync idle state (bit 0)
#define __HWSPI_8BIT_MASTER 0x2f

// Same as previous but data length is 1111 = 15, +1 is 16. 
#define __HWSPI_16BIT_MASTER 0x3f

void SpiInit(void) {
 	PERIP(SPI0_CF) = __HWSPI_16BIT_MASTER + SPI_CF_SRESET;
	PERIP(SPI0_CLKCF) = (2 << 2); //Clock divider = 2+1 (2 waitstates)
	PERIP(GPIO1_MODE) |= (1<<0)|(1<<1)|(1<<2); //Set GPIO1 pins 5,6,7 as peripheral pins. (SCLK, MISO, MOSI)
	GpioSetPin(CSPIN,1); // Chip select high
}
The Arduino's SPI solution is quite easy to initialize, it only requires one method: SPI.begin(). The VS23 reset is also disabled.
The MSP430 uses the USI (Universal Serial Interface) for SPI communication. It requires a bit more work for the initialization, but the comments in the source code file are fairly thorough.

After SpiInit finishes, the execution returns to SpiRamInit, which will attempt to read the manufacturer ID of the VS23S010. Getting the correct ID is a good indication that the SPI communication is working correctly.

Reading the manufacturer ID, like most of the SPI operations, is done with SpiRW. The function can be seen below with reduced comments, see the source code for the full comments.

Code: Select all

u_int16 SpiRW(register u_int16 opcode, register u_int32 address,
				register u_int32 data, u_int16 is16b) {

	u_int16 result = 0;
	SPI_SELECT() // Chip select low (active) 
	
	SpiSendReceiveByte(opcode); // All operations start by sending the opcode

	// Writing the microcode (32 bits)
	if (opcode == PROGRAM) {
		SpiSendReceiveWord(data >> 16);
		SpiSendReceiveWord(data);
		goto END;
	
	// Sending the address if doing a memory operation
	} else if ( (opcode == WRITE) | (opcode == READ) ) {
		if (is16b) address = address << 1;
		SpiSendReceiveByte((address >> 16));
		SpiSendReceiveWord((address));
	}

	// Sending or receiving the data. Used with both register and memory operations.
	if (is16b) result = SpiSendReceiveWord(data);
	else result = SpiSendReceiveByte(data);

	END:
	SPI_DESELECT() // Chip select high
	return result;
}
In short, all SPI operations start with setting the chip select signal low, and then sending the opcode of the desired operation. The VS23S010 has a large collection of opcodes, and they can be found in the datasheet.
With register reads and writes, the data is transmitted next. Each register's read and write operation has their own opcode.
With memory operations the 24-bit address must be sent after the opcode, and the data is transmitted after that. This applies to both reading and writing.
Writing the microcode is different from other operations in that it contains 32 bits, unlike any other register, which are all either 8 or 16 bits.
With SpiRW, memory operations are also either 8 or 16. This is only a limit of the SpiRW function, and the program has examples of doing longer write operations.
After the data has been sent or received, chip select must be set back to high.

SpiRW is identical for Arduino and MSP430, because the SpiSendReceiveByte and SpiSendReceiveWord functions only have internal differences, but the interfaces are the same.

Once the manufacturer ID has been received, the result will be printed out. An error message is printed if the ID is anything else than 0x2Bxx, where xx is the amount of found VS23s minus 1. Therefore a system with one VS23 (like the VS1010 Developer Boards and VS23S010 Breakout Boards) should get the value 0x2B00.

After that the program will return to SpiRamVideoInit. Depending on the platform, some constants may be printed before the next part.



This is the where configuring the VS23 begins. To better understand the significance of the video generation settings, you can read the VS23 Guide, especially the chapter 5.2.

Code: Select all

// Sets the DisROps bit to disable VS23 1-3 in case there are 
// more than one in the system. VS23 0 is left enabled.
SpiRW(WRITE_MULTIIC, 0, 0xe, 0);

// Set SPI memory address to sequential (autoincrementing) operation.
SpiRW(WRITE_STATUS, 0, 0x40, 0);

// Write picture start and end values. These are the 
// left and right limits of the visible picture.
SpiRW(PICSTART, 0, (STARTPIX-1), 1);
SpiRW(PICEND, 0, (ENDPIX-1), 1);
			
// Enable and select PLL clock.
SpiRW(VDCTRL1, 0, (VDCTRL1_PLL_ENABLE) | (VDCTRL1_SELECT_PLL_CLOCK), 1);
In case the system has multiple VS23s, only one of them is left active, and rest are disabled by writing to the Multi-IC Access Control register.
The memory address is set to autoincrementing operation, which makes writing much easier and faster as multiple bytes can written one after another, and the address will automatically increase.
Picture start and end values are set with two separate SPI operations. These define the left and right limits for the x-coordinates. The program is set to use 320x240 resolution for the visible picture. The actual amount of data is different, because prototype lines are also required.
The 8x PLL clock is enabled. This will generate the Video Display Controller clock (VClk) from the 3.579545 MHz crystal.

The memory is also cleared by writing it full of 0. The VS1010 and Arduino do this with a for-loop and SpiSendReceiveWord, which writes 16 bits at once. The MSP430 code uses SpiSendBytes instead. This is because the used chip was originally running at a fairly low clockrate, and writing longer, 255 bytes long blocks was faster because there was less time spent with function calls.

Once the memory is cleared, a couple more register values are set.

Code: Select all

SPI_SELECT()
SpiSendReceiveByte(WRITE); // Send opcode
for (i=0; i<65539; i++) SpiSendReceiveWord(0); // Address and data.
SPI_DESELECT()

// Set length of one complete line (in PLL (VClk) clocks). 
// Does not include the fixed 10 cycles of sync level at the beginning 
// of the lines. 
SpiRW(LINELEN, 0, PLLCLKS_PER_LINE, 1);
	
// Set microcode program for picture lines. Each OP is one VClk cycle.
SpiRW(PROGRAM, 0, ((OP4 << 24) | (OP3 << 16) | (OP2 << 8) | (OP1)), 0);
Line length depends on the video crystal frequency.
The microcode has four bytes, one per operation. In this example the OP4 is empty. You can read about the microcode's purpose and functionality in this forum post.

The next part handles the prototype lines and the index. You can read more about them in this forum post. In short, the index is a list of addresses, and each address points to the data for one line of the picture. The prototype lines are not visible in the final picture, instead they contain other information that is used in displaying the picture, like sync lines and black levels. Visible lines do also have a part of protoline both before and after the visible part.

Code: Select all

// Define where Line Indexes are stored in memory
SpiRW(INDEXSTART, 0, INDEX_START_LONGWORDS, 1);

// Set all line indexes to point to protoline 0 (which by definition
// is in the beginning of the SRAM)
for (i=0; i<TOTAL_LINES; i++) SetLineIndex(i, PROTOLINE_WORD_ADDRESS(0));

// Construct protoline 0
protoline(0, 0, COLORCLKS_PER_LINE, BLANK_LEVEL);
protoline(0, BLANKEND, FRPORCH, BLACK_LEVEL); // Set the color level to black
protoline(0, 0, SYNC, SYNC_LEVEL); // Set HSYNC
protoline(0, BURST, BURSTDUR, BURST_LEVEL); // Set color burst

// Construct protoline 1. This is a short+short VSYNC line
protoline(1, 0, COLORCLKS_PER_LINE, BLANK_LEVEL);
protoline(1, 0, SHORTSYNC, SYNC_LEVEL); // Short sync at the beginning of line
protoline(1, COLORCLKS_LINE_HALF, SHORTSYNCM, SYNC_LEVEL); // Short sync at the middle of line

// Construct protoline 2. This is a long+long VSYNC line
protoline(2, 0, COLORCLKS_PER_LINE, BLANK_LEVEL);	
protoline(2, 0, LONGSYNC, SYNC_LEVEL);	// Long sync at the beginning of line
protoline(2, COLORCLKS_LINE_HALF, LONGSYNCM, SYNC_LEVEL); // Long sync at the middle of line

// Now set first lines of frame to point to prototype lines
for (i=1; i<4; i++) SetLineIndex(i, PROTOLINE_WORD_ADDRESS(1)); // Lines 1 to 3
for (i=4; i<7; i++) SetLineIndex(i, PROTOLINE_WORD_ADDRESS(2)); // Lines 4 to 6
for (i=7; i<10; i++) SetLineIndex(i, PROTOLINE_WORD_ADDRESS(1)); // Lines 7 to 9

// Set pic line indexes to point to protoline 0 and their individual picture line.
for (i=0; i<ENDLINE-STARTLINE; i++) SetPicIndex(i + STARTLINE, PICLINE_BYTE_ADDRESS(i),0);
To keep the code cleaner, three short functions SetLineIndex, SetPicIndex and protoline are used. All of them are in the ntsc.c file.

Before the visible content is written, the control register is set.

Code: Select all

// Enable Video Display Controller, set video mode to NTSC, set program length and linecount.
	SpiRW(VDCTRL2, 0, 
		VDCTRL2_ENABLE_VIDEO | 
		VDCTRL2_NTSC |
		VDCTRL2_PROGRAM_LENGTH |
		VDCTRL2_LINECOUNT, 1);
Setting the enable bit starts the video generation.
The mode is simply a choice between PAL or NTSC.
Program length is set to the length of the microcode, 4 in this case.
Line count is the total amount of lines in one frame, both visible and invisible.

Lastly, the actual visible parts are drawn.

Code: Select all

// Blue background
{
	u_int16 w = BLANKEND;
	i=255;
	while (w < FRPORCH) {
		SpiRW(WRITE, w++, 0x4500+i, 1);
		if (i>102) i--;
	}
}

// Draw picture
for (i=0; i<16; i++) for (j=0; j<16; j++) {
	FilledRectangle((i*20), (j*10), (i*20)+19, (j*10)+9, (j*16)+i); // Draw colored rectangles
	FilledRectangle((i*20), (j*10)+9, (i*20)+19, (j*10)+9, 0); // Draw black horizontal line
	FilledRectangle((i*20)+19, (j*10), (i*20)+19, (j*10)+9, 0); // Draw black vertical line
}
The background area is separate from the actual picture area, and it's drawn with a different method.
The colored grid is created with two for-loops. The FilledRectangle function includes limits to prevent writing outside the picture area. The function also has its own SPI process, similar to the memory clearing that was used earlier.

After the picture has been drawn, the current line pointer's value is printed.

The VS1010B romspecific program can be downloaded from the previous comment. Both .dlx and the source code are available. To find the other versions of the program, use the links at the top of this comment.
Last edited by Kalle on Wed 2017-11-08 15:02, edited 1 time in total.

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

Ntsc10c

Post by Kalle » Wed 2017-11-08 15:01

Ntsc10c is a VS1010C compatible version of the program. It's a straightforward port from the Ntsc10b, and functions exactly the same as the previous version.

The only change to the actual code was made in the main.c. The line

Code: Select all

SetPLL(5);
was added to the beginning of the program. This sets the PLL clock multiplier of the VS1010C to 5. Generally this is not required, but if a previous program changed the multiplier to a higher value, it can cause errors. The SPI will run faster with a higher PLL multiplier, and if the rate is too high, the VS23 can miss some of the sent data. This could lead to errors such as missing pixels in this image.

The .dlx and VSIDE solution are available below.
Attachments
ntsc10c.zip
VSIDE solution
(20.93 KiB) Downloaded 1 time
ntsc10c.dlx
.dlx executable
(4.93 KiB) Downloaded 1 time

Post Reply

Who is online

Users browsing this forum: No registered users