The Records of Recent Matters

Coding for the Dingoo A-320

Introduction

The Dingoo A320 is a Chinese handheld game console, using a form factor very much like the GBA Micro, but running on a 360 MHz MIPS-derivative processor, JZ4732. It has a 320*240 LCD screen and stereo speakers, and internal flash memory and a mini-SD slot for storing files and games.

This page contains what information I have been able to collect about building your own applications for it using the built-in μC/OS-II operating system. It is also possible to install Linux on it and write applications under that, but that will not be covered here.

This document is very unfinished. It will be updated whenever I have time to work on writing code for the Dingoo.

Building applications

The Dingoo will run native applications stored as files with the extension ".app" stored in the internal memory or on a mini-SD card. To run applications, select the "3D Game" option in the main menu.

Compiler

The mipsel-linux-gcc toolchain can be used to build applications for μC/OS-II, if the final linking step is done by a separate application. Ingenic, the makers of the JZ4732 processor, apparently supply pre-built executables for Windows and Linux.

I use Mac OS X, and built a cross-compiling gcc for the mipsel-linux target from scratch. This seems to require disabling quite a few modules, but it is doable.

Executable format

I have yet to delve deeper into the executable format used by μC/OS-II, but apparently it uses simple import and export sections for functions. The import section specifies a jump table and strings naming the functions to import into the jump table. The user application then jumps to entries in the jump table, which the loader fills in at load time, from a static list of imported functions contained in the The Dingoo μC/OS-II kernel.

There are different ways to set this up. The official SDK uses a somewhat unflexible tool named dlmake.exe that only runs on Windows. An Italian going by the name zaxxon has written an alternative linker in Python which is available here: http://a320.forumfree.net/

I use a slightly modified version of this (with the external tools it calls changed to the mipsel-linux ones).

Both use special sections in the ELF file to store the jump tables and function name strings, and special libraries to link with when building to supply these to the linker, but they are not compatible with each other, and you have to pick one or the other to use.

Exported OS functions

To use a function exported by the kernel, you need a jump table entry for it, and a C prototype. libdingoo.a provides jump tables for all the exported functions. There is no complete list of function prototypes, however. I have created a header file named Dingoo.h by compiling information from various sources, which supplies some prototypes for the more commonly used functions.

This forum thread also has some more information about the various functions that are available for import from the OS: http://forum.openhandhelds.org/viewtopic.php?f=35&t=981

μC/OS-II

Some of the exported functions originate in the original μC/OS-II and some are custom functions added by the Dingoo developers. For the μC/OS-II functions (the ones prefixed with OS), the μC/OS-II documentation may be of use.

Hardware register definitions

The JZ4732 processor contains a large amount of integrated hardware that is accessed through memory-mapped registers. JZ4740.h is a C header file that defines hardware registers, constants and other useful functions for accessing all this hardware. This file is a slightly modified version of a header file from the JZ4740 μC/OS-II port.

Processor data sheets that would actually explain what the registers do seem to be very hard to come by, for some reason, but there does actually seem to be a set of them here: http://www.amebasystems.com/downloads/hardware/datasheets/ben-nanonote/Ingenic-SOC-JZ4720/Jz4740-PM/

Housekeeping

The OS wants to do some event processing of its own while your application runs, but this has to be explicitly invoked. The function sys_judge_event() does this processing. It should be called regularly. It returns some kind of return code that can signal that your application should quit, but I have not found out exactly how this works yet.

Doing this enables the user to shut down the console by holding the power button. If you do not call sys_judge_event(), this functionality will not work, which will annoy the user.

Driving the display

The Dingoo uses a display controller tightly integrated with the JZ4732 processor to drive the display. The display controller uses its own internal framebuffer for storing the image that is displayed on the screen. To update the screen, the pixel data to be displayed has to be copied to this internal framebuffer.

The display uses 16-bit RGB 5:6:5 pixels. It may be possible to reconfigure this, but this is yet unexplored.

OS functions

There are two OS function that handle transmitting images to the display:

(There are also functions named _lcd_get_frame() and _lcd_set_frame() but those only call the above functions internally.)

To use these function, simply use the lcd_get_frame() to get a pointer to the pre-allocated temporary framebuffer, then draw your graphics in this buffer, and finally call lcd_set_frame() to copy the temporary framebuffer to the display.

The problem with these functions is that they do not provide a way to do proper double buffering, as there is only one static buffer that can be written to. Most programs solve this by using further temporary framebuffers and copying them to the one returned by lcd_get_frame(). However, this seems needlessly wasteful. It is possible to do the screen update manually, without relying on the OS.

FIFO

The internal framebuffer used by the Smart LCD controller is not memory-mapped, but instead data is transferred to it through a FIFO register. Each write to the SLCD_FIFO register will transfer a single pixel to the framebuffer.

It is possible to use this to manually update the screen. Here is a very simple and silly example that fills the screen with noise:

for(int y=0;y<240;y++)
for(int x=0;x<320;x++)
{
	REG_SLCD_FIFO=rand();
}

DMA

However, normally data is moved through the FIFO using a DMA transfer. The built-in lcd_set_frame() function just sets up a DMA transfer from the temporary framebuffer to the FIFO. We can do the same thing ourselves. lcd_set_frame() does something mostly equivalent to the following code:

void SendLCDFrame(uint16_t *frame)
{
	// Wait for transfer terminated bit
	while(!(REG_DMAC_DCCSR(0)&DMAC_DCCSR_TT));

	// Enable DMA on the SLCD.
	REG_SLCD_CTRL=1;

	// Disable DMA channel while configuring.
	REG_DMAC_DCCSR(0)=0;

	// DMA request source is SLCD.
	REG_DMAC_DRSR(0)=DMAC_DRSR_RS_SLCD;

	// Set source, target and count.
	REG_DMAC_DSAR(0)=((uint32_t)frame)&0x1fffffff;
	REG_DMAC_DTAR(0)=SLCD_FIFO&0x1fffffff;
	REG_DMAC_DTCR(0)=320*240*2/16;

	// Source address increment, source width 32 bit,
	// destination width 16 bit, data unit size 16 bytes,
	// block transfer mode, no interrupt.
	REG_DMAC_DCMD(0)=DMAC_DCMD_SAI|DMAC_DCMD_SWDH_32|
	DMAC_DCMD_DWDH_16|DMAC_DCMD_DS_16BYTE|DMAC_DCMD_TM;

	__dcache_writeback_all();

	// No DMA descriptor used.
	REG_DMAC_DCCSR(0)|=DMAC_DCCSR_NDES;

	// Set enable bit to start DMA.
	REG_DMAC_DCCSR(0)|=DMAC_DCCSR_EN;
}

lcd_set_frame() also uses an interrupt to detect when the transfer is done, but this is not actually necessary, and the OS supplies no way for applications to install their own interrupt handlers. (It might be possible to replace the entire interrupt handling infrastructure with your own, but this has not been explored yet.)

The advantage of this function is that it takes a pointer to the framebuffer to send to the display. The pointer returned from lcd_get_frame() can be passed in, in which case it will behave almost exactly like lcd_set_frame(), or you can malloc() your own framebuffer (of size 320*240*2) to do double buffering. Using lcd_get_frame() for one buffer and allocing a second one is recommended, to save a bit of memory, since the OS-supplied buffer will always be allocated.

Audio output

The JZ4732 contains built-in hardware for driving an audio codec. This hardware uses DMA transfers to a FIFO to send PCM data to the codec, similarly to how the Smart LCD controller works.

The kernel provides a set of functions prefixed waveout_ to handle audio output. I have not yet investigated these, but the Dingoo.h header provides prototypes and some data structures for them.

(Here again there are duplicate functions prefixed with a single underscore, but these also just call the un-prefixed versions internally, and should be ignored.)

Reading buttons

The Dingoo kernel provides a few functions for reading the state of the buttons on the device.

The bits in these bit strings are arranged as follows:

Button: Up Down Left Right A B X Y L R Select Start Power
Bit: 20 27 28 18 31 21 16 6 8 29 10 11 7

On my hardware, at least, it seems it is not possible to press Y and B at the same time, although most other combinations seem to work. (It's been suggested that firmware version 1.20 fixes this, I have not yet tested this.)

Timing

The kernel exports a number of (somewhat redundant) functions useful for timing and waiting:

Due to the problems with GetTickCount(), OSTimeGet() seems to be the best choice for doing timing. Its resolution is somewhat low, though. The only way to get better timing is to set up one of the eight TCU timers to do timing for you, but since there is no reliable way to install interrupts, and these timers are limited to 16 bits, this might also be somewhat tricky to get right.

Also, I have not investigated which of the TCU timers are actually free for use.

Accessing the filesystem

The Dingoo kernel provides functions for accessing the filesystem on the internal memory and any external mini-SD card. These functions are familiar POSIX-like functions, with some extensions. However, all functions are prefixed with fsys_. So fopen() is actually fsys_fopen().

Confusingly, the kernel also provides non-prefixed versions of some calls, like fread() and printf(), but these only use the built-in serial port (which is as far as I know not externally accessible at all, unless you solder wires onto the circuit board for it). Avoid these functions.

Others