C0-microSD+ Host Interface and Protocol
The C0-microSD+ communicates with a host over the SD interface, using either the 4-wire SD protocol or SD-over-SPI. All communication is achieved through host-initiated block read and write operations, the same operations a host uses for any SD card. The module never initiates a transfer of its own and raises no interrupt toward the host, so a host application that needs to observe device state polls the relevant address. For transport-level details, see SD Interface.
Communication over the SD interface uses block read and write operations, not the SDIO extension of the SD interface. No additional host drivers are required.
The address map of the C0-microSD+ is unified. The host and the RISC-V core inside the
Signaloid SoC use the same addresses for every register and every buffer, so the
tables on this page are the single address reference for both sides. There is no separate
host-side offset map, and there is nothing to translate between the two views. To read the
STATUS register, a host application seeks to 0x08208000 on the block device and reads
four bytes, and the device application reads that same register at 0x08208000.
Unified address map
The map below runs from the highest address at the top to the lowest address at the bottom. Dashed regions are reserved and are not decoded by the module.
The labels to the right of the map show how far each side reaches. A host over the SD
interface can address every region from the bottom of the SPI flash up to and including the
device identity region, which ends at 0x0FFFE004. The Signaloid SoC can access all of those
regions and, in addition, the DMA registers above them. What a host can read and write within
that address range also depends on whether the core is running. See
SD access while the core runs.
The device identity region is therefore the highest address a host can access. The DMA registers are the only decoded region above it, and the SD interface cannot access them. A host drives the module through the CSRs and the MMIO buffer. The DMA controller is internal to the module, so only a device application running on the Signaloid SoC can program it. See DMA registers.
| Region | Address range | Size |
|---|---|---|
| SPI flash, bitstream | 0x00000000 – 0x001FFFFC | 2 MiB |
| SPI flash, user data | 0x00200000 – 0x00FFFFFC | 14 MiB |
| Flash OTP | 0x08000000 – 0x080001FC | 512 bytes |
| LRAM, main memory | 0x08100000 – 0x0814FFFC | 320 KiB |
| CSR registers | 0x08200000 – 0x08214003 | See the register table |
| MMIO buffer | 0x08300000 – 0x0830FFFC | 64 KiB |
| Device identity | 0x0FFFE000 – 0x0FFFE004 | 8 bytes |
| DMA registers | 0x10000000 – 0x1000001C | 32 bytes |
The two SPI flash regions together form the 16 MiB of on-board flash. The Signaloid-supplied
FPGA bitstream lives in the bitstream region at 0x00000000, and your device application is
flashed into the user data region at 0x00200000. Flashing an application needs no unlock,
provided that the core is stopped. Writes into the bitstream region are rejected unless the
BITSTREAM_UNLOCK register at 0x08214000 holds the unlock key, so an accidental host write
cannot access the region that the module boots from.
The LRAM region is the 320 KiB of main memory that the device application links its code and
data into. That describes a lram build. A device application built with MODE set to
xip executes in place from the user data region of the SPI flash instead, and the LRAM
holds only its writable data.
SD access while the core runs
What a host can access over the SD interface depends on whether the Signaloid SoC core is running. While the core is running, the SD interface cannot access the SPI flash or the flash OTP window. A host read of either returns zeros, and a host write to either is discarded.
| Core state | Host reads the flash and the OTP | Host writes the flash |
|---|---|---|
| Running | Blocked, and read back as zeros | Blocked, and discarded |
| Stopped, bitstream locked | Allowed | User data region only |
| Stopped, bitstream unlocked | Allowed | Allowed, including the bitstream |
The rule applies to the entire SPI flash, both the bitstream region and the user data
region, together with the flash OTP window at 0x08000000. Every region above the flash stays
accessible whether the core is running or stopped, so a host can always read and write the
LRAM, the CSRs, the MMIO buffer, and the device identity region. Your device application keeps
its own access to the flash at all times, because this rule applies only to the SD interface.
Three consequences follow:
- Reading the bitstream region back requires a stopped core. So does reading the bitstream metadata, and so does verifying the bitstream against its recorded CRC.
- Reading the flash OTP requires a stopped core. The module serial number and UUID are stored in the OTP.
- A host cannot write the user data region while your application is running. Stop the core
before you flash from the host side.
C0_SD_toolkit.pystops the core for you.
Stop the core with the config core-stop action and start it again with config core-start.
See Starting and stopping the core.
A host read of the flash while the core is running, returns zeros rather than an error. A tool that does not check the core state first reports an all-zero image rather than a failure. We advise you to verify the core state before you conclude that a module has lost its contents.
Device identity region
The C0-microSD+ has a hard-wired, read-only region that names the module. The region exists above the SPI flash, accessible from a host in either core state. Query this region to identify a module without stopping an application running on C0-microSD+.
| Region | Base address | Size | Access | Contents |
|---|---|---|---|---|
| Device identity | 0x0FFFE000 | 8 bytes | Read-only | S-C0-01 and a null byte |
The device identity region stores ASCII text. A hexdump of the eight bytes starting at 0x0FFFE000 reads
S-C0-01 followed by a terminating null byte. Read the region as a string and compare all
eight bytes instead of individual bytes.
The C0_SD_toolkit.py script reads the identity region to identify which module it is talking to,
which is why the --variant option is optional. See
Identify Your Module.
SPI flash write and erase semantics
Writing to the SPI flash is not like writing to memory. The flash erases in 4 KiB sectors, and the module performs that erase for you, so a single write can discard data you never meant to touch. The rules below apply to every write that reaches the flash, whether it comes from the host over the SD interface or from your device application.
Writes erase whole sectors
A write to a 4 KiB-aligned address erases that whole 4 KiB sector before it writes, which discards everything else the sector held. A write long enough to cross into the next sector erases that sector too, at the point where it crosses.
A write that starts part way into a sector erases nothing. It writes into a sector that still holds its previous contents, and flash bits only ever clear, so the stored result combines the old and the new values instead of matching what you wrote.
Two approaches follow from this. Write whole 4 KiB sectors, so that each write starts on a sector boundary and replaces the whole sector, or read a sector, change the bytes you need, and write the whole sector back.
Allow for the write latency
A flash write does not complete until the flash has finished erasing and programming, which
takes far longer than a write to the LRAM or to the MMIO buffer. There is no
busy flag to poll, because the write itself blocks until the flash is done. In an
xip build the same wait also stalls the core, which fetches its instructions from that
flash.
Writes are word-sized and word-aligned
Every flash write updates a full 32-bit word, and the address must be word-aligned. The SPI flash has no byte-granular write, so writing a single byte still programs the whole word that contains it, carrying whatever the other three bytes of that word hold.
The bitstream region lock
The C0-microSD+ is an FPGA-based module, and the bitstream region configures the FPGA to implement the Signaloid SoC and the SD interface itself. The bitstream region is locked when the module powers up. While it is locked, a write into the region does nothing, and the module still reports that write as successful, so confirm any intentional change by reading it back. The lock governs writes only, and it applies to a host over the SD interface and to a device application alike. A separate rule removes host access to the whole SPI flash while the core is running, regardless of the state of the lock. See SD access while the core runs. Unlocking the bitstream region is only needed to install an official Signaloid bitstream update. Flashing a device application does not need it.
Writing a wrong or corrupted image in the bitstream region leaves the module permanently inoperable. Keep the region locked.
The lock covers writes from the host and from your device application alike. Because the hardware clears the unlock key whenever the core is running, the region is unconditionally locked while a device application executes, unlocking requires the core to be stopped first, and starting the core re-locks the region immediately. See BITSTREAM_UNLOCK register.
To change the bitstream, use the C0_SD_toolkit.py script, which stops the SoC core, unlocks
the region, writes it, verifies it, and locks it again. If the region is still locked when a
write arrives, the module drops the write without reporting an error, so it surfaces as a
verify mismatch from the toolkit rather than as a failed write.
The unlock key is volatile. A power cycle returns BITSTREAM_UNLOCK to 0x00000000, so the
region comes back locked and no recovery step is needed after a power loss.
Keep application data clear of the binary
The module flashes your device application at 0x00200000, at the start of the user data
region, and nothing protects it. A write that erases any sector the binary occupies corrupts
the binary, and the core then runs a damaged image the next time you start it.
Place persistent application data well above the binary. The top of the user data region,
for example the last megabyte from 0x00F00000, is a safe home for it. An xip build runs
directly from the user data region, so a device application that writes to the flash must
stay clear of its own image. C0_SD_toolkit.py stops the core for you before it flashes. If
you flash from your own host code, stop the core first.
Never remove power while a flash write is in progress. A sector caught part way through an erase or a program holds neither its old contents nor its new contents. See Handling and ESD.
For the device-application side of this, including where to place persistent data and how to read it back safely, see Using the SPI Flash.
MMIO memory map and registers
The C0-microSD+ realizes the shared
MMIO Interfacing Model
in the Signaloid SoC. The control and status registers, referred to as the CSRs, occupy the
region based at 0x08200000. Every CSR is 32 bits wide.
| Register | Address | Size | Written by | Read by | Reset value |
|---|---|---|---|---|---|
COMMAND | 0x08200000 | 4 bytes | Host | Device | 0x00000000 |
CONFIG | 0x08204000 | 4 bytes | Host, device | Host, device | 0x00000000 |
STATUS | 0x08208000 | 4 bytes | Device | Host | 0x00000000 |
SD_CONFIG | 0x0820C000 | 4 bytes | Host | Host | 0x00000001 |
TRAP_MCAUSE | 0x08210000 | 4 bytes | Trap handler | Host, device | 0xFFFFFFFF |
TRAP_MEPC | 0x08210004 | 4 bytes | Trap handler | Host, device | 0x00000000 |
TRAP_MTVAL | 0x08210008 | 4 bytes | Trap handler | Host, device | 0x00000000 |
BITSTREAM_UNLOCK | 0x08214000 | 4 bytes | Host | Host | 0x00000000 |
COMMANDcarries an application-defined operation code from the host to the device application. The host writes it to request work, and the device application reads it.CONFIGcarries the core reset, LED, and debug pin controls. The bit assignments are in CONFIG register bits.STATUScarries an application-defined status value from the device application back to the host. The device application writes it, and the host polls it.SD_CONFIGcontrols how the module handles the CRC of an incoming SD block write. Leave its defaults in place unless you are bringing up an SD host whose CRC generation you are still debugging.BITSTREAM_UNLOCKgates writes to the bitstream region of the SPI flash. Write the ASCII keyUNLKto unlock the region and any other value to lock it. The core must be stopped for a key write to take effect. See BITSTREAM_UNLOCK register.
The Reset value column gives what each register holds when the module powers up. Every
CSR is volatile, so a power cycle returns all of them to these values. A CONFIG of
0x00000000 holds the core in reset, which means the module always powers up with your
device application stopped. See
Power Cycles and State.
The values carried by COMMAND and STATUS are application-defined. The Signaloid
device-side headers define four conventional SignaloidSoCStatus values, but your
application is free to define its own.
The device-side C API reaches these registers through helper functions rather than through
raw pointers, for example C0HALGetCommandRegister(), C0HALSetStatusRegister(), and
C0HALGetConfigRegister(). See
Developing UxHw Applications.
Trap registers
The Signaloid UxHw® Toolchain bundles a trap handler with every device application it builds for the C0-microSD+. When the RISC-V core takes a trap, execution vectors to that handler, the handler publishes the cause of the trap into the three trap registers where a host can read it, and it then halts the core. A trap therefore stops your device application rather than resuming it, and the trap registers hold the cause of that halt. They are the first place to look when a device application stops responding.
TRAP_MCAUSE holds the trap cause, TRAP_MEPC holds the program counter at the point of
the trap, and TRAP_MTVAL holds the associated trap value. The three registers are
contiguous, so a host can read all twelve bytes in a single transaction starting at
0x08210000. Because the handler halts the core before returning, the values stay stable
while you read them, and they remain readable over the SD interface after the application
has stopped.
TRAP_MCAUSE reads 0xFFFFFFFF when the core has recorded no trap. Treat that value as
no trap recorded rather than as a valid RISC-V cause code, and check it before you read
any meaning into TRAP_MEPC and TRAP_MTVAL. A TRAP_MCAUSE value other than
0xFFFFFFFF means the trap handler ran and halted the core rather than the application
stalling.
Any other value is a standard RISC-V machine-mode exception code. The codes below are the ones a device application on this module can realistically record, because the core runs in machine mode only and has no virtual memory.
TRAP_MCAUSE | Meaning |
|---|---|
0 | Instruction address misaligned |
1 | Instruction access fault |
2 | Illegal instruction |
3 | Breakpoint |
4 | Load address misaligned |
5 | Load access fault |
6 | Store or AMO address misaligned |
7 | Store or AMO access fault |
11 | Environment call from M-mode |
The encoding is the one defined by the RISC-V privileged specification, which lists every architecturally defined code. A value with its most significant bit set denotes an interrupt rather than an exception.
A recorded trap is therefore the first thing to look for when a device application stops responding, because the application does not resume on its own. Note the three values, stop the core, correct the application, flash it again, then start the core.
The most common cause is an unaligned memory access, because the RISC-V core of this module requires every load and store to be naturally aligned. See Using the Hardware Abstraction Layer → Memory alignment.
CONFIG register bits
| Bit | Name | Usage |
|---|---|---|
| 0 | rstn | Core reset, active low. Set to 1 to run the core. |
| 1 | Reserved | Unassigned. Has no effect. Write as 0. |
| 2 | swLedEnable | Hand LED control to software. |
| 3 | swLed | Software-controlled LED state. |
| 4 | redLed | On-board red LED. |
| 5 | greenLed | On-board green LED. |
| 6 | blueLed | On-board blue LED. |
| 7 | debugPin0 | Debug pin 0 output state. |
| 8 | debugPin1 | Debug pin 1 output state. |
| 9 | debugPin2 | Debug pin 2 output state. |
| 31:10 | Reserved | Write as 0. |
Every bit resets to 0 when the module powers up, which holds the core in reset. Unlocking
the bitstream region is done through the BITSTREAM_UNLOCK register at 0x08214000, not
through CONFIG. See BITSTREAM_UNLOCK register.
CONFIG is a single register, so read it, modify the bits you want, and write the whole
32-bit value back. Writing a bare bit mask clears every other control in the register,
including rstn, which halts a running core.
BITSTREAM_UNLOCK register
BITSTREAM_UNLOCK sits at 0x08214000 and is the only control over whether the bitstream
region of the SPI flash accepts writes. No CONFIG bit affects it. The register holds a
single 32-bit key field and resets to 0x00000000.
| Key value | Effect |
|---|---|
0x4B4C4E55, the ASCII bytes UNLK | The region accepts writes. |
Any other value, including 0x00000000 | The region rejects writes. |
The key is a little-endian word, so a hexdump of the register reads U, N, L, K in
order while the region is unlocked.
The hardware forces the key to 0x00000000 whenever the Signaloid SoC core is running. Three
consequences follow.
- The bitstream region is always locked while a device application executes.
- Unlocking requires the core to be stopped first. See Starting and stopping the core.
- A key write issued while the core is running is discarded, and the register reads back as
0x00000000.
Starting the core re-locks the region immediately, so an unlock lasts only as long as the core stays stopped.
The key gates writes only. The core must be stopped to allow the host to read the bitstream region back and verify it. See SD access while the core runs and The bitstream region lock.
DMA registers
The Signaloid SoC includes a DMA engine based at 0x10000000. The SD interface cannot access
it, so a device application is the only thing that programs it. The block holds eight 32-bit
registers.
| Register | Address | Access | Purpose |
|---|---|---|---|
IRQ_CTRL | 0x10000000 | Write | Interrupt enable, in bit 0. |
ACK | 0x10000004 | Write | Acknowledge strobes that return the engine to idle. |
STATUS | 0x10000008 | Read | Engine state and the three error latches. |
SOURCE | 0x1000000C | Write | Address the engine reads from, for a copy. |
LENGTH | 0x10000010 | Write | Number of bytes to transfer. |
DESTINATION | 0x10000014 | Write | Address the engine writes to. |
CONTROL | 0x10000018 | Write | Operation code. Sampled only while the engine is idle. |
SET | 0x1000001C | Write | Value the engine writes, for a set operation. |
STATUS reports the engine state in bits 25:24, alongside three sticky error latches.
| Bits | Name | Meaning |
|---|---|---|
| 0 | unsupported_op | The engine received an operation code it does not implement. |
| 8 | write_error | A write failed, so the destination is in an unknown state. |
| 16 | read_error | A read failed, so the operation could not proceed. |
| 25:24 | state | 0 Idle, 1 Busy, 2 Done, 3 Error. |
ACK clears the latches and returns the engine to idle. Bit 0 acknowledges an error and bit 8
acknowledges the completion interrupt, so writing 0xFFFFFFFF covers both. The engine never
returns to idle on its own.
CONTROL takes one of four operation codes.
| Value | Name | Effect |
|---|---|---|
0 | kDMAOpInvalid | Reserved. The engine enters the Error state. |
1 | kDMAOpCopy | Copy LENGTH bytes from SOURCE to DESTINATION. |
2 | kDMAOpWordSet | Fill LENGTH bytes at DESTINATION with the whole of SET. |
3 | kDMAOpByteSet | Fill LENGTH bytes at DESTINATION with the low byte of SET. |
IRQ_CTRL, ACK, and STATUS hold 0x00000000 on power-up. SOURCE, LENGTH,
DESTINATION, CONTROL, and SET have no defined power-up value, so write every register an
operation needs before you start that operation.
Drive the engine through the dmaTryRun() function instead of directly accessing these registers.
The function enforces several rules that the hardware does not, and a mistake at
this level has no diagnostic. See
Using the DMA Engine and the Cache.
Data buffers
The C0-microSD+ provides one 64 KiB MMIO buffer based at 0x08300000. The module
decodes it as a single region, and the address map above shows it that way.
Software divides that one region down the middle into two halves of 32 KiB each, an output half for data travelling from the device to the host and an input half for data travelling from the host to the device. The division is a convention rather than a hardware boundary. The Signaloid-Compute-Module-Utilities package and the device-side hardware abstraction layer both present the two halves as separate buffers, which keeps application code on each side clear about the direction it is reading or writing.
| Half | Direction | Address range | Size | Device operation |
|---|---|---|---|---|
| Output (MISO) | Device to host | 0x08300000 – 0x08307FFC | 32 KiB | W |
| Input (MOSI) | Host to device | 0x08308000 – 0x0830FFFC | 32 KiB | R |
- MOSI, Manager-Out, Subordinate-In, carries operand data from the host application to the device application.
- MISO, Manager-In, Subordinate-Out, carries result data from the device application back to the host application.
The host writes operands into the input half and reads results back from the output half. The device application does the reverse, reading its operands from the input half and writing its results into the output half. Because the address map is unified, the host and the device application address the same byte of the buffer by the same address.
The device-side C API exposes each half as a set of typed arrays, for example
kC0HALInputBufferFloat and kC0HALOutputBufferFloat, together with the Uint8, Uint32,
Int32, and Double variants and a matching length constant for each array.
When you build a device application with ENABLE_DEBUG_LOGGING, the logging support in
C0Logger.h claims a 512-byte window at the top of the output half of the buffer and writes
log records into it. Keep your result data clear of that window in debug builds, and read
the records back with the C0_debug_logger.py script from the
Signaloid-Compute-Module-Utilities repository.
Communication model, a polled round trip
The module raises no interrupt toward the host, so the host discovers state changes by polling the read-only registers. A typical command round trip runs as follows.
- The host writes operand data into the input half of the MMIO buffer, starting at
0x08308000. - The host writes an application-defined operation code to the
COMMANDregister at0x08200000. - The device application reads
COMMAND, setsSTATUSat0x08208000to its calculating value, and processes the operands. - The device application writes its results into the output half of the MMIO buffer,
starting at
0x08300000, and setsSTATUSto its done value. - The host polls
STATUSuntil it reads the completion value. - The host reads the results from the output half of the buffer, then clears
COMMANDto acknowledge the round trip. The device application returns to its waiting for command state.
The Signaloid device-side headers define four conventional status values.
| Value | Name | Meaning |
|---|---|---|
0 | kSignaloidSoCStatusWaitingForCommand | Idle and ready to accept a new command. |
1 | kSignaloidSoCStatusCalculating | Processing the requested command. |
2 | kSignaloidSoCStatusDone | A result is available in the output buffer. |
3 | kSignaloidSoCStatusInvalidCommand | The requested command was not recognized. |
The values of COMMAND and STATUS are application-defined, but the round-trip mechanics
above are common to every C0-microSD+ application. Open the block device so that each polling
read bypasses the host page cache, because a poll loop over a buffered descriptor can spin on
a stale status value forever. On Linux, open /dev/sdX with O_DIRECT and keep every
transfer block-aligned. On macOS, use the raw /dev/rdiskN node. The Python host interface
does this for you. See
Using the Python Host Interface → Device Access Model.
Starting and stopping the Core
The RISC-V core is released from reset and halted again through bit 0 (rstn) of the
CONFIG register. Setting the bit starts the core running the flashed application, and
clearing it stops the core. The C0_SD_toolkit.py script from the
Signaloid-Compute-Module-Utilities repository wraps both operations,
and it takes the device path as a positional argument before the command.
# Start the core running the flashed application
sudo python3 C0_SD_toolkit.py /dev/disk4 config core-start
# Stop the core when the workload is complete
sudo python3 C0_SD_toolkit.py /dev/disk4 config core-stop
To reset the core, stop it, wait about one second, then start it again. C0_SD_toolkit.py
stops the core for you when it flashes, so stop it yourself only when you flash from your own
host code. A stopped core is a precondition for unlocking the bitstream region, because the
hardware forces the unlock key to 0x00000000 while the core is running. It is also a precondition
for any host access to the SPI flash or the flash OTP. See
SD access while the core runs. A device application
can also halt itself by calling C0HALStopCore(). For the full procedure, see
Start and stop the core.
Power cycles and state
Every register in the module is volatile. The module powers up with the core held in reset and every register at its default value, so a power cycle stops your device application and discards the state that it held.
What a power cycle resets
| State | Survives a power cycle |
|---|---|
| Device application binary in the SPI flash | Yes |
| Data your application wrote to the SPI flash | Yes |
| FPGA bitstream | Yes |
Every CSR, including CONFIG, STATUS, and BITSTREAM_UNLOCK | No |
| Contents of the LRAM | No |
| Contents of the MMIO buffer | No |
| State of the DMA engine | No |
Your device application restarts from the beginning with its variables reinitialized, rather
than resuming where it stopped. Because CONFIG returns to 0x00000000, a power cycle also
returns the LEDs to their default behavior, and because BITSTREAM_UNLOCK returns to
0x00000000, the bitstream region comes back locked.
The trap registers survive stopping and starting the core, which is what lets you read them after the trap handler halts a crashed application, but a power cycle clears them to their reset values. Read them before you power-cycle a module that you are debugging.
The DMA engine also survives a core reset, so stopping and starting the core leaves the engine in whatever state it was in. Only a power cycle clears it. See Using the DMA Engine and the Cache.
Recovering after a power cycle
Bring the module back to a working state in three steps.
- Read the CSRs to confirm that the module is present and responding.
- Start the core, which the module left in reset.
- Write back any
CONFIGandSD_CONFIGvalues that your application relies on, because the module restored their defaults.
Change CONFIG by reading it, modifying the bits you need, and writing the whole value
back. See CONFIG register bits.
Hosts that power down an idle module
Some SD host controllers like laptop card readers power down a card that has been idle and then power it back up on the next transaction. Expect this if your host application leaves the module idle for long periods.
The module comes back with the core in reset, so your device application has stopped and its
state is gone. Detect this by reading the CONFIG register, which returns 0x00000000 after
a power-down.
To prevent the power-down, read from the module at least once per second while it is otherwise idle. A read of any CSR is enough to keep the host controller from cutting power.
Host application example
The example below runs on a macOS host with the module at /dev/disk4. It writes the value
0x00000001 to the COMMAND register. Because the address map is unified, the host seeks to
the same address that the device application uses to read the register.
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
int
main(void)
{
char * devicePath = "/dev/disk4";
uint32_t command = 0x00000001;
off_t commandRegisterAddress = 0x08200000;
int fd;
off_t seekResult;
ssize_t writeResult;
/*
* Open the C0-microSD+ device, committing writes synchronously.
*/
fd = open(devicePath, O_WRONLY | O_SYNC | O_DSYNC);
if (fd == -1)
{
perror("Error opening device");
return -1;
}
/*
* Seek to the address of the COMMAND register.
*/
seekResult = lseek(fd, commandRegisterAddress, SEEK_SET);
if (seekResult == (off_t)-1)
{
perror("Error seeking to target address");
close(fd);
return -1;
}
/*
* Write the COMMAND register data.
*/
writeResult = write(fd, &command, sizeof(uint32_t));
if (writeResult != sizeof(uint32_t))
{
perror("Error writing data to the device");
}
close(fd);
return 0;
}
Reading the STATUS register follows the same shape, with O_RDONLY in place of O_WRONLY
and a seek to 0x08208000. Header files and helper functions for building C and Python host
applications are in the Signaloid-Compute-Module-Utilities repository,
and the Python package is published as signaloid-utilities. For a complete worked round
trip, see Examples and Demos.
Next steps
- Using the Hardware Abstraction Layer, the C library that wraps these registers and buffers on the device side.
- Using the Python Host Interface, the package that wraps them on the host side.
- Using the DMA Engine and the Cache, driving the DMA registers above from a device application.
- MMIO Interfacing Model, the model these registers and buffers implement.
- Using the C0_SD_toolkit, reading and writing the same registers from the command line.