This is a minimal monitor program for μ'nSP MCUs with UART, and an example project with a minimal Makefile setup. It is targeted for GPL95101UB, but you can replace body files to target different μ'nSP MCUs.
You need the μ'nSP IDE to get the toolchain. Download it from here.
This section will offer some info on the various parts of the project. Some command line arguments for the various tools are explained, and for those that aren't, please consult μ'nSP Programming Tools User's Manual.
The code is written in μ'nSP 1.3 assembly rather than C. Your project should have the following interrupt handlers declared public:
_BREAK
_FIQ
_RESET
_IRQ0
_IRQ1
_IRQ2
_IRQ3
_IRQ4
_IRQ5
_IRQ6
_IRQ7
At a minimum your firmware must have _RESET
. If it's not present, the linker
will crash.
__sn_sp_val
is a pointer to the bottom of the SRAM, for putting your stack
at. This symbol is automatically provided by the linker.
Generally you will want to include the body header file so you can use names and constants for registers.
A function looks like this:
FuncName: .proc
push bp to [sp] // Save caller bp
sp -= 1 // Allocate locals
bp = sp + 1 // bp points to first local
// Save first arg to local
r1 = bp + 4 // First arg is 3 past previous value of sp
// If you didn't save bp yet, it would have
// been sp + 3. Here, go past allocated local,
// saved bp, and sr/pc to get to the first arg
[bp] = r1 // Save to local, note you can use offsets
// when doing memory accesses with bp
// Call another function
sp -= 1 // Allocate stack for args
r2 = sp + 1 // Get address of first arg
[r2] = r1 // Put value into arg
call AnotherFunc // Call the function
// Core pushes next pc and current sr to stack
// Return value is in r1
sp += 1 // Deallocate args
sp += 1 // Deallocate locals
pop bp from [sp] // Restore saved bp
retf // Return from function
// pop bp, pc from [sp] // You can also do the previous two
// instructions with one. It pops bp, sr, pc
// in that order
.endp
You can set a local label by prefixing or postfixing it with ?
.
The assembler takes in a .asm
file and outputs a .obj
file. Specify the
ISA version so the assembler can generate the correct code, especially if you
happen to be using μ'nSP 2.0's extended instructions. The -t
argument sets
the version, e.g. -t4
for μ'nSP 2.0.
You can add include paths for the assembler to look through. Use the -I
argument. Note that the search path starts in the same folder as the source
file you're including into, then using the include paths (relative to working
directory, if you're using relative paths).
For linker, most options are described in the programming tools manual. Note the following specifically:
-infblk
: Insert a particular binary blob. This is generally used for MCUs that don't use the standard ROM header but a different one. In the case of GPL951, it uses a SPIF-specific block with some data for setting up faster SPI flash access. The data format of this blob (where it's located and its length) is described in the programming tools manual.-injcks
: Injects checksum for custom ROM header. Arguments:-injcks <checksum_start_addr> <checksum_len> <checksum_res_addr> <num_checksums> <entry_point_ptr_addr>
checksum_start_addr
: address to start checksumming fromchecksum_len
: number of words to include in checksum calculationchecksum_res_addr
: address to write checksum tonum_checksums
: number of copies of checksum to writeentry_point_ptr_addr
: address to write entry point to (22-bit address)
-at
: Use automatic mode, outputting in task format (binary). Compare to-a
, which is automatic mode with output specified in.ary
file (or S37 by default), and-as
, which is automatic mode with output in S37 format.
The body file is somewhat complex and generated by μ'nSP IDE. It tells the linker how big SRAM and ROM is, and where to place code by default.
There are two linker scripts used: a .ary
file and a .lik
file. The .ary
file is an automatic mode script where you could simply indicate the .obj
files you want to link. When operated in automatic mode, the linker will use
the .ary
to generate a .lik
. The .lik
file specifies the objects to link,
the output location and format, and where sections from each file are located.
You can modify this file to pin sections to specific addresses, and the linker
will retain this when regenerating the file from the .ary
script.
To pin a section, uncomment its Locate
line from the .lik
script, and set
the address after at
. If you intend to move the section to somewhere else
(such as from flash to RAM so you can disable flash to do something), specify
an address after linkat
to have the linker use that as a base for that
section when you are using labels.
The .map
file shows a table of global symbols, info about sections within
each .obj
, total memory usage, and some additional information. You can use
this to verify that code is where you want it to be. The .smy
file is an
extract of the memory summary section. This is generally printed by the IDE
at the end of the build.
This is a table for initialized ram sections. It is usually processed before
running _main
by the startup code. The format is as follows:
struct init_table_item {
void *dest;
uint src_ds; // upper 6 bits of source address
uint src_offset;
uint length; // in words
};
struct init_table {
int num_items;
struct init_table_item items[num_items];
};
It appears a minimum of two items are reserved by the linker regardless whether there is any initialized data needed in RAM.
The linker outputs in the usual ROM image format, but for GPL951, it uses a
different ROM format which starts at 0x9000
. To get a ready-to-flash image,
you need to remove the first 0x9000
words from the output. The stripper
program will let you cut the binary in place. I originally tried to use dd
for this, but it didn't deal very well with trying to do things in-place.
The monitor program's protocol is fairly simple. It starts by setting up PWM (for driving the backlight on Punitapi-chan, which is what this was originally written for), and inits UART at 38400 baud, 8 bits, no parity, 1 stop bit. The PWM output is at 10% before UART init and 100% after UART init. This was originally mostly for debugging, since when it works you can't see the difference that quickly. The SPIF interface is also turned off by this point, with all code runnng from RAM.
All data sent and received from the monitor are in words. The monitor loops, expecting a command word. After receiving the command, it will respond with the same word so the other end can verify sending and receiving is working.
The following commands are available:
Receives a fixed word response. The monitor responds with 0x4948
.
Reads from the address space, limited to segment 0. Arguments:
addr
: the address to start reading fromcount
: the number of words to send
The monitor will send the required number of words starting from the address.
It finished by sending 0xaabb
.
Writes to memory, limited to segment 0. Arguments:
addr
: the address to start writing tocount
: the number of words to write
The monitor reads the next count
of bytes and stores them starting from the
address. It finished by sending 0xccdd
.
Calls a function that takes no arguments. Arguments:
addr
: the address of the code to call, segment 0 only
The monitor responds with 0xeeff
and calls the code at the address.
The monitor will respond with 0x474e
if an invalid command is given.
A host program for interfacing with the monitor can be found here.
This is an aside regarding GPL951xxUx boot ROM behavior when used with SPI flash. For the boot ROM to pass execution to code in SPI flash, the following conditions must be met:
- The first 32 bytes of flash consists of the string
GP-SPIF-HEADER--0123456789abcdef
- The two dwords immediately following the string are equal to each other (this is the ROM checksum, however the boot ROM does not validate it; it is left to the SPIF calibration code after it sets up the SPIFC for faster access)
- The dword at 0x903e is a pointer to the ROM's entry point
Although there is code for setting SPIF clock and timing, it does not appear to be referenced.