Periphery

To operate on a peripheral the user first needs to acquire a lock on the specific peripheral. On every subsequent operation on the peripheral the API is checking the validity of the lock. This is to avoid coterminous access by other tasks. After this, the device needs to be configured before it can be used.

Every read and write access exists in two variants: synchronous and asynchronous.

CAN

A Controller Area Network (CAN bus) is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other's applications without a host computer.

Let get right into it by aquiring and closing the needed lock.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"

int main() {
  usoc_peripheral_lock_t lock;
  usoc_can_config_t config;

  usoc_peripheral_get(&lock, USOC_PER_CAN0);

  //useage of CAN

  usoc_peripheral_release(&lock, USOC_PER_CAN0);
}

CAN is meant to have at least two communication partners to work. A CAN bus with just one participant is considered to be faulty. Since we do not want to attach a device in this tutorial we will use the CAN loopback mode. It enables us to speak to ourself. Every message sent is immeditatly returned back to us.

To use this mode it has to be enabled through the config usoc_can_config_t. The loopback is of the type usoc_can_loopback_t and has three modes: no loopback, internal loopback and external loopback. For this example, the internal looback will be used. Furthermore, we will disable shutdown.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_can.h"

int main() {
  usoc_peripheral_lock_t lock;
  usoc_can_config_t config;

  config.loopback = CAN_LOOPBACK_INT;
  config.shutdown_en = 0;

  usoc_peripheral_get(&lock, USOC_PER_CAN0);
  usoc_peripheral_config(&lock, USOC_PER_CAN0, &config);

  //useage of CAN

  usoc_peripheral_release(&lock, USOC_PER_CAN0);
}

Now we are ready to send our first message, which in the CAN world is also called a frame. Each has a header and body.

Let us start by defining the header usoc_can_tx_header_t. It consists of a frame identifier id and a length of the contained data len. The identifier can be one of two sizes as defined by the enum usoc_can_id_type_t. Either 11 bit which corresponds to the base frame format or 29 bit which corresponds to the extended frame format. The contained data can also be one of two types as defined by the enum usoc_can_rtr_t. It is either a data frame which is used to send data or a remote frame which is used to request data.

In our tutorial we will use a fantasy id of 1, encoded with 11 bit and we will send 8 byte of data in a data frame.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_can.h"

uint8_t can_txdata[] = {0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08};

int main() {
  usoc_peripheral_lock_t lock;
  usoc_can_config_t config;
  usoc_can_tx_header_t hdr;

  config.loopback = CAN_LOOPBACK_INT;
  config.shutdown_en = 0;

  hdr.id = 1;
  hdr.id_type = CAN_ID_STD;
  hdr.len = 8;
  hdr.rtr = CAN_RTR_DATA;

  usoc_peripheral_get(&lock, USOC_PER_CAN0);
  usoc_peripheral_config(&lock, USOC_PER_CAN0, &config);

  usoc_printf("CAN Write\n");
  usoc_can_write(&lock, USOC_PER_CAN0, &hdr, can_txdata, 0, 0);

  usoc_peripheral_release(&lock, USOC_PER_CAN0);
}

How can we check that the send data is send correctly? With the loopback mode enabled the same message we send will be received. Because this happens immeditatly, the read needs be asynchronous and to be registed before the write. To enable the possiblity that the callback can be handled usoc_idle needs to be called.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_can.h"

uint8_t can_txdata[] = {0x0C, 0x0A, 0x0F, 0x0E, 0x0B, 0x0A, 0x0B, 0x0E};
uint8_t can_rxdata[8];
uint8_t rx_flag = 0;

void rx_callback(void *arg, uint32_t size) {
    usoc_printf("CAN RX CALLBACK %u:\n", size);
    for(uint32_t i = 0; i < size; i++) {
      usoc_printf("%X ", can_rxdata[i]);
    }
    usoc_printf("\n");
    rx_flag = 1;
}

int main() {
    usoc_peripheral_lock_t lock;
    usoc_can_config_t config;
    usoc_can_tx_header_t hdr;
    uint32_t read_size;

    config.loopback = CAN_LOOPBACK_INT;
    config.shutdown_en = 0;

    hdr.id = 1;
    hdr.id_type = CAN_ID_STD;
    hdr.len = 8;
    hdr.rtr = CAN_RTR_DATA;

    usoc_peripheral_get(&lock, USOC_PER_CAN0);
    usoc_peripheral_config(&lock, USOC_PER_CAN0, &config);

    usoc_can_read(&lock, USOC_PER_CAN0, can_rxdata, &read_size, rx_callback, 0);

    usoc_printf("CAN Write\n");
    usoc_can_write(&lock, USOC_PER_CAN0, &hdr, can_txdata, 0, 0);
    
    while(!rx_flag)
        usoc_idle();

    usoc_peripheral_release(&lock, USOC_PER_CAN0);

    return(0);  
}

The output of the program looks like this:

CAN Write
CAN RX CALLBACK 8:
C A F E B A B E

The data we send out is also received.

I2C

I2C is a serial communication bus, which allows easy communication to peripheral ICs. This tutorials consists out of two parts: synchron and asynchron. To simplify the tutorial, the asynchronous part is based on the synchronous.

Ganymed has multiple I2C busses connected, which can run up to 400kHz. In this tutorial we will focus on the MIS2DH accelerometer which is connected to the I2C0 bus. Your board might have different periphery depending on the configuration you bought. In this case you need to change the I2C Bus, slave address, subadresses and the data you send to the chip. Look up these informations in your datasheets.

I2C Synchron

First we have to accquire a lock on the bus we want to use and after usage it needs to be released. For usoc_peripheral_get the header file usoc_user_services.h is used. For usoc_peripheral_release the header file usoc_peripheral_API.h.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2c.h"

int main() {
  usoc_peripheral_t i2c = USOC_PER_I2C0;

  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  //Accquire lock
  periph_error=usoc_peripheral_get( &lock, i2c );
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //Release lock
  periph_error=usoc_peripheral_release( &lock,  i2c );
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }
}

The next step is to configure the bus. The max bitrate for the device is specified at 100000 bit/s.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2c.h"

int main() {
  usoc_peripheral_t i2c = USOC_PER_I2C0;

  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_i2c_config_t i2c_config={100000, PULL_2k};

  //Accquire lock
  periph_error=usoc_peripheral_get(&lock, i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //Configure peripheral
  periph_error=usoc_peripheral_config(&lock, i2c, (void*)&i2c_config);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_peripheral_release(&lock, i2c);
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //Release lock
  periph_error=usoc_peripheral_release(&lock, i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }
}

Now the bus is ready to be used.

In the datasheet for the MIS2DH the slave address 00110xb is given. Because the SA0 pad is connected to high, the X value is 1 which results in the address 0011001 in binary or 0x19. The subadress is used to adress which register is being read or written. We want to read out from the who_am_i register which is specified as the subadress 0x0F and to print it out. The expected output should be 0x33.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2c.h"

int main() {
  uint8_t recv_buffer[1];

  usoc_peripheral_t i2c = USOC_PER_I2C0;

  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_i2c_config_t i2c_config={100000, PULL_2k};

  //Accquire lock
  periph_error=usoc_peripheral_get(&lock, i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //Configure peripheral
  periph_error=usoc_peripheral_config(&lock, i2c, (void*)&i2c_config);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_peripheral_release(&lock,  i2c);
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  usoc_printf("Read from I2C0!\n");

  //Read
  periph_error=usoc_i2c_read(&lock, i2c, 0x19, 0x0F, 1, recv_buffer, 1, 0, 0);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_peripheral_release(&lock, i2c);
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  usoc_printf("RX0: 0x%02x\n", recv_buffer[0]);

  //Release lock
  periph_error=usoc_peripheral_release(&lock, i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }
}

I2C Asynchron

Maybe you already noticed the two additional parameters for usoc_i2c_read. They are used for the asynchron variant.

First we move our print of the recv_buffer in its own function and make the buffer global. To set this function as call back it is passed as arugment to usoc_i2c_read. It is nesseray to idle until the asynchronous function has been called. To achieve this we add an read flag and an idle while loop.

#include <stdint.h>
#include <stdbool.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2c.h"

uint8_t recv_buffer[1];

bool read = false;

void i2c_callback(void *arg) {
  usoc_printf("RX0: 0x%02x\n", recv_buffer[0]);

  read = true;
}

int main() {
  usoc_peripheral_t i2c = USOC_PER_I2C0;

  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_i2c_config_t i2c_config={100000, PULL_2k};

  //Accquire lock
  periph_error=usoc_peripheral_get(&lock, i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //Configure peripheral
  periph_error=usoc_peripheral_config(&lock, i2c, (void*)&i2c_config);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_peripheral_release(&lock,  i2c);
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  usoc_printf("Read from I2C0!\n");

  //Read
  periph_error=usoc_i2c_read(&lock, i2c, 0x19, 0x0F, 1, recv_buffer, 1, i2c_callback, 0);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_peripheral_release(&lock,  i2c);
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }

  //wait for read flag, go to idle
  while(!read) usoc_idle();

  //Release lock
  periph_error=usoc_peripheral_release(&lock,  i2c);
  if(periph_error!=USOC_PER_ERR_NONE) {
    usoc_printf("ERROR %i\n", periph_error);
    return -1;
  }
}

I2S

I2S is an electrical serial bus used for connecting digital audio devices together.

I2S Synchron

First we have to accquire a lock on the bus we want to use and after usage it needs to be released.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"

int main() {
  usoc_peripheral_t peri = USOC_PER_I2S0;
  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_printf("I2S%u:\n", peri - USOC_PER_I2S0);

  periph_error = usoc_peripheral_get(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_peripheral_release(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  usoc_printf("Done\n");
  return 0;
}

There are different I2S endpoints with different settings. To enable a correct way to communicate, the periphery needs to be configured. For this the usoc_i2s_config_t structure is used. The first parameter specifies the baudrate and the second the bit width of the bus.

#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"

int main() {
  usoc_peripheral_t peri = USOC_PER_I2S0;
  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_i2s_config_t i2s_config = {16000, I2S_32_BIT};

  usoc_printf("I2S%u:\n", peri - USOC_PER_I2S0);

  periph_error = usoc_peripheral_get(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_peripheral_config(&lock, peri, &i2s_config);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_peripheral_release(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  usoc_printf("Done\n");
  return 0;
}

Now its time to get our first data from the device.

For that the usoc_i2s_capture function is used. To save the recived data, an adequate buffer is needed.

We also add a small loop to print out the received data. Because the recv_buffer is uint8_t but we read 32 bit from the I2S periphery, the buffer will be converted to uint32_t.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2s.h"

#define RX_SIZE 64

int main() {
  uint8_t recv_buffer[RX_SIZE];

  usoc_peripheral_t peri = USOC_PER_I2S0;
  usoc_peripheral_error_t periph_error;
  usoc_peripheral_lock_t lock;

  usoc_i2s_config_t i2s_config = {16000, I2S_32_BIT};

  usoc_printf("I2S%u:\n", peri - USOC_PER_I2S0);

  periph_error = usoc_peripheral_get(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_peripheral_config(&lock, peri, &i2s_config);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_i2s_capture(&lock, peri, recv_buffer, RX_SIZE, 0, 0);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  periph_error = usoc_peripheral_release(&lock, peri);
  if(periph_error != USOC_PER_ERR_NONE) {
      usoc_printf("ERROR %i\n", periph_error);
      return -1;
  }

  for(int i = 0; i < RX_SIZE/4; i++) {
    usoc_printf("RX: 0x%08x\n", ((uint32_t*)recv_buffer)[i]);
  }

  usoc_printf("Done\n");
  return 0;
}

I2S Asynchron

Lets assume that everthing will work and no errors will happen. This allows us to simplify the code.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2s.h"

#define RX_SIZE 64

int main() {
  uint8_t recv_buffer[RX_SIZE];

  usoc_peripheral_t peri = USOC_PER_I2S0;
  usoc_peripheral_lock_t lock;

  usoc_i2s_config_t i2s_config = {16000, I2S_32_BIT};

  usoc_printf("I2S%u:\n", peri - USOC_PER_I2S0);

  usoc_peripheral_get(&lock, peri);
  usoc_peripheral_config(&lock, peri, &i2s_config);
  usoc_i2s_capture(&lock, peri, recv_buffer, RX_SIZE, 0, 0);
  usoc_peripheral_release(&lock, peri);

  for(int i = 0; i < RX_SIZE/4; i++) {
    usoc_printf("RX: 0x%08x\n", ((uint32_t*)recv_buffer)[i]);
  }

  usoc_printf("Done\n");
  return 0;
}

Now we move our print into its own function and use it as call back. As always we have to idle till the event happens.

#include <stdint.h>
#include <stdbool.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_i2s.h"

#define RX_SIZE 64

uint8_t recv_buffer[RX_SIZE];

bool read = false;

void callback(void *arg) {
  for(int i = 0; i < RX_SIZE/4; i++) {
    usoc_printf("RX: 0x%08x\n", ((uint32_t*)recv_buffer)[i]);

    read = true;
  }
}

int main() {
  usoc_peripheral_t peri = USOC_PER_I2S0;
  usoc_peripheral_lock_t lock;

  usoc_i2s_config_t i2s_config = {16000, I2S_32_BIT};

  usoc_printf("I2S%u:\n", peri - USOC_PER_I2S0);

  usoc_peripheral_get(&lock, peri);
  usoc_peripheral_config(&lock, peri, &i2s_config);
  usoc_i2s_capture(&lock, peri, recv_buffer, RX_SIZE, callback, 0);

  while(!read) usoc_idle();

  usoc_peripheral_release(&lock, peri);

  usoc_printf("Done\n");
  return 0;
}

IO

The ganymed has multiple GPIO pins which can be controlled in software. These pins use a logic level of 1.8 or 3.3 volt. Please look up into the pin description of the datasheet, which pin has which level.

In this tutorial we will control the state of the pins directly. Like always we have to aquire a lock on the periphery before we can use it. After its usage it gets released.

#include "usoc_peripheral_API.h"

int main() {
  usoc_peripheral_lock_t lock;

  usoc_peripheral_get(&lock, USOC_PER_IO0);

  usoc_peripheral_release(&lock, USOC_PER_IO0);

  return 0;
}

To tell the hardware which pin we want to use as input or as output we have to configure the periphery. For simplify every pin is set to output which results in 0xFFFFFFFF.

#include "usoc_peripheral_API.h"
#include "usoc_peripheral_io_lo.h"
#include "usoc_peripheral_io_hi.h"

int main() {
  usoc_io_config_t io_config={0xFFFFFFFF, 0, 0, 0xFFFFFFFF};

  usoc_peripheral_lock_t lock;

  usoc_peripheral_get(&lock, USOC_PER_IO0);

  usoc_io_config(&lock, USOC_PER_IO0, &io_config);

  usoc_peripheral_release(&lock, USOC_PER_IO0);

  return 0;
}

Now we are able to control the pins. The mask desides which pins are controlled.

The pin 15 will be used for demo. To generate the mask for this pin the binary represnetation of a 1 will be used and shifted 14 times to the left (1<<14). The pins are low activated, which means 0 turns them on and 1 turns them off.

Let's set this pin to 0 and every other pin to 1. To do this task the usoc_io_set_state function is used. For the second call the mast is negated which means, that ever other pin is affected.

#include <stdint.h>
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_io_lo.h"
#include "usoc_peripheral_io_hi.h"

int main() {
  uint32_t ctr=(1<<14);
  usoc_io_config_t io_config={0xFFFFFFFF, 0, 0, 0xFFFFFFFF};

  usoc_peripheral_lock_t lock;

  usoc_peripheral_get(&lock, USOC_PER_IO0);

  usoc_io_config(&lock, USOC_PER_IO0, &io_config);

  usoc_io_set_state(&lock, USOC_PER_IO0, 1, ctr);
  usoc_io_set_state(&lock, USOC_PER_IO0, 0, ~ctr);

  usoc_peripheral_release(&lock, USOC_PER_IO0);

  return 0;
}

You should now to be able to see the led turning on. This pin will stay activate until it is explicitly deactived or till the reset button is pressed. With a sleep and a loop you are now able to program your own blinking led program.

Pulse-width modulation

Pulse-width modulation (PWM), is a method of reducing the average power delivered by an electrical signal, by effectively chopping it up into discrete parts.

Besides the lock and the periphery usoc_io_pwm_enable needs parameters of which pin needs to be configured. The other two parameters are the frequency at which the pin toggls between on and of and an parameter which defines the percantage of the time the pin is in the on state. With usoc_io_pwm_disable the PWM is turned off for the specified pin.

#include <stdint.h>
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_io_lo.h"
#include "usoc_peripheral_io_hi.h"

int main() {
  uint32_t ctr=(1<<14);
  usoc_io_config_t io_config={0xFFFFFFFF, 0, 0, 0xFFFFFFFF};

  usoc_peripheral_lock_t lock;

  usoc_peripheral_get(&lock, USOC_PER_IO0);

  usoc_io_config(&lock, USOC_PER_IO0, &io_config);

  usoc_io_pwm_enable(&lock, USOC_PER_IO0, 15, 10, 5);

  //do something else

  usoc_io_pwm_disable(&lock, USOC_PER_IO0, 15);

  usoc_peripheral_release(&lock, USOC_PER_IO0);

  return 0;
}

Interrupts

The Ganymed processor has the ability to watch for certain changes on the IO pins. If the defined change happen, a callback function will be executed. In usoc_io_interrupt_t the different types of detectable changes are defined. The type NONE will disable the interrupts.

The code is the same like setting PWM for the pins directly, but instead you use usoc_io_interrupt_config.

#include <stdint.h>
#include <stdbool.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_io_lo.h"
#include "usoc_peripheral_io_hi.h"

bool triggered = false;

void callback(void *arg,  uint32_t pin) {
  usoc_printf("interrupt!\n");
  triggered = true;
}

int main() {
  uint32_t ctr=(1<<14);
  usoc_io_config_t io_config={0xFFFFFFFF, 0, 0, 0xFFFFFFFF};

  usoc_peripheral_lock_t lock;

  usoc_peripheral_get(&lock, USOC_PER_IO0);

  usoc_io_config(&lock, USOC_PER_IO0, &io_config);

  usoc_io_interrupt_config(&lock, USOC_PER_IO0, 15, HIGH_LEVEL, callback, 0);

  while(!triggered) usoc_idle();

  usoc_io_interrupt_config(&lock, USOC_PER_IO0, 15, NONE, callback, 0);

  usoc_peripheral_release(&lock, USOC_PER_IO0);

  return 0;
}

SPI

The Serial Peripheral Interface (SPI) is a synchronous serial communication interface specification used for short-distance communication.

Ganymed has multiple SPI busses connnected, which can run up to 50MHz. For this tutorial we will use the SPI0 device. Lets start like always with acquireing a lock on the SPI interface and releasing it afterwards.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_spi.h"

int main() {
  usoc_peripheral_lock_t lock;

  //get peripheral
  usoc_peripheral_get(&lock, USOC_PER_SPI0);

  //release the SPI peripheral
  usoc_peripheral_release(&lock, USOC_PER_SPI0);

  return 0;
}

SPI uses a master-slave architecture. The Ganymed is the master and an attached devices is the slaves. You can also connect another device to a slave, in which case the slave becomes the master and the newly connected device the slave. All of the communicate with each other simultaneously because of the used full-duplex mode.

To configure an SPI bus we have to specify a baudrate and an SPI mode. The baudrate is the frequency of the communication and as already mentioned can be up to 50MHz which translates to 50000000 as value. The mode is either 0, 1, 2, or 3. It is a combination of two binary values, CPOL and CPHA, translated into a decimal number.

ModeCPOLCPHA
000
101
210
311

CPOL defines in which state the Clock Idle is. DPHA 0 means that the Clock Idle is Low. CPHA defines the Clock Phase. CPHA 0 means that the data after the first edge is used.

You need to consult the documentation of your slave device to find out which mode to choose. If you want to know more about the inner workings of SPI, read this Wikipedia page.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_spi.h"

int main() {
  usoc_peripheral_lock_t lock;
  usoc_spi_config_t spi_config={1000000, MODE_0};

  //get peripheral
  usoc_peripheral_get(&lock, USOC_PER_SPI0);

  //configurate peripheral
  usoc_peripheral_config(&lock, USOC_PER_SPI0, (void*)&spi_config);

  //release the SPI peripheral
  usoc_peripheral_release(&lock,  USOC_PER_SPI0);

  return 0;
}

Now it is time to write some data to the periphery. To do so, we have to create a buffer which contains the data to send and one which receives the incoming data. Both of them need to have the same size because in SPI, for every bit that we send we will receive one.

Let us send 6 bytes by calling the function usoc_spi_readwrite.

The function expects eight parameters. The first and the second one are lock and the addressed periphery respectively. The following 0 denotes the selected chip. The fourth and fifth parameter are the buffers we just created, followed by their size. The seventh parameter is a callback function which will be executed when the transmission is complete and which can take a user argument given as the last parameter. Since we do not demonstrate the callback here, we just pass a 0 to those last two.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_spi.h"

#define TRANSMIT_SIZE   6

int main() {
  uint8_t send_buffer[TRANSMIT_SIZE] = {0, 1, 2, 3, 4, 5};
  uint8_t recv_buffer[TRANSMIT_SIZE];

  usoc_peripheral_lock_t lock;
  usoc_spi_config_t spi_config={1000000, MODE_0};

  //get peripheral
  usoc_peripheral_get(&lock, USOC_PER_SPI0);

  //configurate peripheral
  usoc_peripheral_config(&lock, USOC_PER_SPI0, (void*)&spi_config);

  //transmit with CS = 0
  usoc_spi_readwrite(&lock, USOC_PER_SPI0, 0, send_buffer, recv_buffer, TRANSMIT_SIZE, 0, 0);

  //release the SPI peripheral
  usoc_peripheral_release(&lock, USOC_PER_SPI0);

  return 0;
}

To check if the transmission was successfull we print out the send and recived data.

#include <stdint.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_spi.h"

#define TRANSMIT_SIZE   6

int main() {
  uint8_t send_buffer[TRANSMIT_SIZE] = {0, 1, 2, 3, 4, 5};
  uint8_t recv_buffer[TRANSMIT_SIZE];

  usoc_peripheral_lock_t lock;
  usoc_spi_config_t spi_config={1000000, MODE_0};

  //get peripheral
  usoc_peripheral_get(&lock, USOC_PER_SPI0);

  //configurate peripheral
  usoc_peripheral_config(&lock, USOC_PER_SPI0, (void*)&spi_config);

  //transmit with CS = 0
  usoc_spi_readwrite(&lock, USOC_PER_SPI0, 0, send_buffer, recv_buffer, TRANSMIT_SIZE, 0, 0);

  //release the SPI peripheral
  usoc_peripheral_release(&lock, USOC_PER_SPI0);

  usoc_printf("Send data:\n");
  
  for(int i=0;i<TRANSMIT_SIZE;i++) {
    usoc_printf("TX%u: 0x%02x\n", i, send_buffer[i]);
  }

  usoc_printf("Recived data:\n");

  for(int i=0;i<TRANSMIT_SIZE;i++) {
    usoc_printf("RX%u: 0x%02x\n", i, recv_buffer[i]);
  }

  return 0;
}

The output should look like this:

Send data:
TX0: 0x00
TX1: 0x01
TX2: 0x02
TX3: 0x03
TX4: 0x04
TX5: 0x05
Recived data:
RX0: 0x00
RX1: 0x01
RX2: 0x02
RX3: 0x03
RX4: 0x04
RX5: 0x05

UART

A universal asynchronous receiver-transmitter (UART) is used for asynchronous serial communication in which the data format and transmission speeds are configurable. You can read more about it on Wikipedia.

The Ganymed has three UART interfaces UART0, UART1 and UART2 which support up to 2Mbit. The first one is used internally by usoc_printf. Thus, if you lock it, subsequent calls to printf will fail.

In this tutorial we are going to print a string to the console of your development computer which is already attached to the Ganymed via UART, to be more precise via UART0. As always, we will acquire a lock, send data over UART and release the lock again.

#include "usoc_peripheral_API.h"
#include "usoc_peripheral_uart.h"

int main()
{
  usoc_peripheral_lock_t lock;

  //get peripheral
  usoc_peripheral_get(&lock, USOC_PER_UART0);

  //release UART peripheral
  usoc_peripheral_release(&lock,  USOC_PER_UART0);

  return 0;
}

The next step is to configure the periphery.

The first configuration option is parity which stands for the parity bit. It serves as a simple data verification mechanism to detect errors in the sent bits. It might be 1 for example if the data stream contained an even number of ones.

You have to decide which one you want to use and you also have to look into the specification of your connected UART device to find out what it is capable of. In our tutorial we set the parity to PAR_NONE which basically means that the parity check is disabled. This works for most of the TTL-to-USB adapters out there.

You can find the possible settings defined in the enum usoc_uart_config_t.

With the next configuration option tx_timeout you can force the function to quit waiting for an answer after a certain amount of time given in milliseconds. In our tutorial we wait for 1 second.

With stopbits option you can chose one of the three possibilities 1, 1.5 and 2 which are declared in the enum usoc_uart_stop_t. Where speed is tight, and your UART only offers division ratios in powers of 2, adding an extra stopbit can be an option to give a less drastic reduction in speed than the next lowest baudrate. We will just set it 1 stop bit.

The baudrate is also specific to your attached device. 1000000 is a good value for TTL-to-USB adapters like the one you are using to display prints in a terminal.

#include "usoc_peripheral_API.h"
#include "usoc_peripheral_uart.h"

int main() {
  
  //get peripheral
  usoc_peripheral_lock_t lock;
  usoc_peripheral_get(&lock, USOC_PER_UART0);

  //configurate peripheral
  usoc_uart_config_t uart_config;
  uart_config.parity = PAR_NONE;
  uart_config.rx_timeout = 1000;
  uart_config.stopbits = STOP_1;
  uart_config.baudrate = 1000000;

  usoc_peripheral_config(&lock, USOC_PER_UART0, (void*)&uart_config);

  //release UART peripheral
  usoc_peripheral_release(&lock,  USOC_PER_UART0);

  return 0;
}

After configuring we can get to the exciting part and write some actual data to UART. We do that by calling the function usoc_uart_write. We send the string "HELLO!\n", encoded in ASCII.

#include <stdint.h>
#include <stddef.h>
#include "usoc_user_services.h"
#include "usoc_peripheral_API.h"
#include "usoc_peripheral_uart.h"

#define BUFFER_SIZE   128

int main() {
  //get peripheral
  usoc_peripheral_lock_t lock;
  usoc_peripheral_get(&lock, USOC_PER_UART0);

  //configurate peripheral
  usoc_uart_config_t uart_config;
  uart_config.parity = PAR_NONE;
  uart_config.rx_timeout = 1000;
  uart_config.stopbits = STOP_1;
  uart_config.baudrate = 1000000;

  usoc_peripheral_config(&lock, USOC_PER_UART0, (void*)&uart_config);

  //print via UART
  uint8_t msg[BUFFER_SIZE] = "Hello!\n";
  uint32_t len = 8;

  usoc_printf("Print via UART0!\n");
  usoc_uart_write(&lock, USOC_PER_UART0, msg, &len, NULL, NULL);

  //release UART peripheral
  usoc_peripheral_release(&lock,  USOC_PER_UART0);

  return 0;
}

If you are using an device to communicate with UART0 then the output should look like this:

Print via UART0!
Hello!