SAM V71 Xplained Ultra - Willzyax/FlatSat-SAMV71-I2C GitHub Wiki
This wiki page describes how the SAM V71 development board can be setup to debug FlatSat code remotely. It should possible to run the complete FlatSat code on the board, however, this was not implemented and this page only describes how to run FreeRTOS, I2C and some other functionalities on the SAMV71 board. For a detailed explanation of all functionalities the reader can find more information on the Microchip Microchip website and in the board's datasheet for driver details. Note that Microchip refers to the I2C protocol as the Two-Wired Interface (TWI). For flashing the software to the board and for debugging of the software, the Microchip Studio IDE is used. The FreeRTOS manual can be found on their website. The functions and examples used here are completely transferrable between the FlatSat OBC and the development board.
This online tool offers the possibility to either consult existing examples or to construct your own project. For our purposes the main software components to add are FreeRTOS and I2C. The image below also depicts different possible settings for the I2C communication. All these settings can be added, removed or changed afterwards in the IDE as well. The pins are selected, the desired driver is selected and the bus clock speed can be set. The SERIAL driver is included to make it possible to send data over USB to the PC, the DELAY and WDT are optional and can be used to implement delays and watchdog timers. On the left-hand side PINMUX can be selected to have more control over pinout settings, and CLOCKS can be selected to have full control over internally generated clock settings. When generating a project this way, a folder with example files and code is added as well, in which the basic usages of the different software components are highlighted.
The reader might notice, when going through the source files of a project, that there are hal hpl hri folders. As stated by Microchip and in the ASF Reference Manual: The driver layer is divided into Hardware Abstraction Layer (HAL), Hardware Proxy layer (HPL), and Hardware Register Interface (HRI) layer. HAL remains common across devices, and HPL and HRI vary according to the hardware used. The HAL folder contains hardware independent/agnostic API, for hardware with the same kind of functionality. The interface, which most users will use, is in the HAL layer. Hardware aware functionality is needed by the HAL, is implemented in the hardware aware HPL level, keeping the HAL level hardware agnostic. HRI level functions are used to configure register bits or bitfields.
There are lots of examples available from Atmel Start. However, to make some basic functionalities clear, some examples are described here.
A project from Atmel Start is downloaded including the FreeRTOS software component. We will try to make the onboard LED blink via an RTOS task.
- In the main file, add the task_led to the functions. The toggle pin level function is defined in the hpl_gpio_base file.
static void task_led(void *p)
{
(void)p;
for (;;) {
gpio_toggle_pin_level(LED0);
os_sleep(1000);
}
}
- Add the task memory stack size and priority, and define the task handler.
#define TASK_LED_STACK_SIZE (108 / sizeof(portSTACK_TYPE))
#define TASK_LED_STACK_PRIORITY (tskIDLE_PRIORITY + 1)
static TaskHandle_t xCreatedLedTask;
- Add the xTaskcreate (from the FreeRTOS API) in main() and call upon the task scheduler.
xTaskCreate(task_led, "Led", TASK_LED_STACK_SIZE, NULL,TASK_LED_STACK_PRIORITY, &xCreatedLedTask);
vTaskStartScheduler()
- Add the LED0 pin in atmel_start_pins.h
#define LED0 GPIO(GPIO_PORTA, 23)
- Initiate the pin by adding the necessary commands in driver_init.c:
gpio_set_pin_level(LED0,
// <y> Initial level
// <id> pad_initial_level
// <false"> Low
// <true"> High
true);
// Set pin direction to output
gpio_set_pin_direction(LED0, GPIO_DIRECTION_OUT);
gpio_set_pin_function(LED0, GPIO_PIN_FUNCTION_OFF);
- Note that you can refer to for example LED0 for the pin by either using a void pointer, void *p = LED0, or by using the Atmel form in which it is saved, which in this case is uint32, thus uint32_t p = LED0.
- Another typical way to handle delays is by getting the clock tick count in the begin of the program and wait for a certain amount of ticks between parts of the program. This can be done using the following commands for a delay of 1000 ms:
TickType_t xLastWakeTime = xTaskGetTickCount();
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
- main code
#include "atmel_start.h"
#include "atmel_start_pins.h"
#include "hal_io.h"
#include "hal_rtos.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#define TASK_LED_STACK_SIZE (108 / sizeof(portSTACK_TYPE))
#define TASK_LED_STACK_PRIORITY (tskIDLE_PRIORITY + 1)
static TaskHandle_t xCreatedLedTask;
/**
* OS task that blinks LED
*/
static void task_led(void *p)
{
(void)p;
for (;;) {
gpio_toggle_pin_level(LED0);
os_sleep(1000);
}
}
/**
* \brief Create OS task for LED blinking
*/
static void task_led_create(void)
{
/* Create task to make led blink */
//xTaskCreate(task_led, "Led", TASK_LED_STACK_SIZE, NULL, TASK_LED_STACK_PRIORITY, &xCreatedLedTask);
if (xTaskCreate(task_led, "Led", TASK_LED_STACK_SIZE, NULL, TASK_LED_STACK_PRIORITY, &xCreatedLedTask) != pdPASS) {
while (1) {
;
}
}
}
static void tasks_run(void)
{
vTaskStartScheduler();
}
int main(void)
{
atmel_start_init();
task_led_create();
tasks_run();
return 0;
}
This example demonstrates use of the I2C read and write functions and how to print something to the data visualiser tool in Microchip Studio. The I2C and Serial software components have to be included in Atmel Start. Do not forget to activate the necessary pins and, optionally, clock speed, for I2C communication. The Serial component is used for USART communication with the PC, and thus to print data to the data visualiser tool. In this example no RTOS was used, but the code can be easily altered to include RTOS and make it compatible with the FlatSat OBC.
- Pins PA3 and PA4 (SDA and SCL respectively) should already be defined in atmel_start_pins for I2C, as well as the RX and TX pins for USART communication
#define PA3 GPIO(GPIO_PORTA, 3)
#define PA4 GPIO(GPIO_PORTA, 4)
#define PA21 GPIO(GPIO_PORTA, 21)
#define PB4 GPIO(GPIO_PORTB, 4)
- The driver_init file should contain the init functions to set up the I2C and USART buses.
- As in the FlatSat & I2C example, set up the I2C parameters. Similar procedure for USART.
struct io_descriptor *I2C_0_io;
i2c_m_sync_get_io_descriptor(&I2C_0, &I2C_0_io);
i2c_m_sync_enable(&I2C_0);
i2c_m_sync_set_slaveaddr(&I2C_0, 0x11, I2C_M_SEVEN);
struct io_descriptor *io_serial;
usart_sync_get_io_descriptor(&SERIAL, &io_serial);
usart_sync_enable(&SERIAL);
- The Serial interface makes use of str_write and read functions, which in themselves use the io_read and write functions (the same as the ones used for I2C communication) over the predefined USART ports. This function can be used to write/read over Serial.
static void str_write(const char *s) {
io_write(&EDBG_COM.io, (const uint8_t *)s, strlen(s));
}
- The itoa function, part of the stdlib C library, can for example be used to convert variables to printable strings.
- main code example, which uses I2C to communicate with two INA219 current & voltage sensors:
#include <atmel_start.h>
#include <stdio.h>
#define INA219_1_ADDRESS 0x40
#define INA219_2_ADDRESS 0x41
#define INA219_CONFIG_BVOLTAGERANGE_16V 0x0000 // 16 V bus range
#define INA219_CONFIG_GAIN_1_40MV 0x0000 // Gain 1, 40 mV
#define INA219_CONFIG_BADCRES_12BIT 0x0180 // 1 x 12-bit shunt sample
#define INA219_CONFIG_SADCRES_12BIT_1S_532US 0x0018 // 1 x 12-bit shunt sample
#define INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS 0x07 // < shunt and bus voltage continuous
#define INA219_REG_SHUNTVOLTAGE (0x01)
#define INA219_REG_BUSVOLTAGE (0x02)
#define INA219_REG_POWER (0x03)
#define INA219_REG_CURRENT (0x04)
#define INA219_REG_CONFIG 0x00
#define INA219_REG_CALIBRATION 0x05
#define INA219_VSHUNT_PGA1_MASK 0x8FFF
#define INA219_VBUS_MASK 0xFFF8
#define STR_DATA_SIZE 60
#define STR_INFO_SIZE 80
void retrieve_voltage(uint8_t sensor, uint16_t *V_bus, int16_t *V_shunt);
int main(void)
{
/* Initializes MCU, drivers and middleware */
atmel_start_init();
/* initialize USART for PC communication, variable definitions */
struct io_descriptor *io_serial;
usart_sync_get_io_descriptor(&SERIAL, &io_serial);
usart_sync_enable(&SERIAL);
char str_data[STR_DATA_SIZE], str_info[STR_INFO_SIZE], vbus_str[10], vshunt_str[10];
/* I2C (TWI) setup */
struct io_descriptor *I2C_0_io;
i2c_m_sync_get_io_descriptor(&I2C_0, &I2C_0_io);
i2c_m_sync_enable(&I2C_0);
int16_t V_shunt_1 = 0, V_shunt_2 = 0;
uint16_t V_bus_1 = 0, V_bus_2 = 0;
/* set configuration register for both sensors
* also set the calibration register if you want to measure current and power directly
*/
uint16_t config = INA219_CONFIG_BVOLTAGERANGE_16V |
INA219_CONFIG_GAIN_1_40MV | INA219_CONFIG_BADCRES_12BIT |
INA219_CONFIG_SADCRES_12BIT_1S_532US |
INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
i2c_m_sync_set_slaveaddr(&I2C_0, INA219_1_ADDRESS, I2C_M_SEVEN);
i2c_m_sync_cmd_write(&I2C_0, INA219_REG_CONFIG, &config, 2);
i2c_m_sync_set_slaveaddr(&I2C_0, INA219_2_ADDRESS, I2C_M_SEVEN);
i2c_m_sync_cmd_write(&I2C_0, INA219_REG_CONFIG, &config, 2);
while (1) {
// sensor 1 & 2 measurements
retrieve_voltage(INA219_1_ADDRESS,&V_bus_1,&V_shunt_1);
retrieve_voltage(INA219_2_ADDRESS,&V_bus_2,&V_shunt_2);
snprintf(str_data, STR_DATA_SIZE,"Vs1(uV),%d,Vb1(mV),%d,Vs2(uV),%d,Vb2(mV),%d\n", V_shunt_1, V_bus_1, V_shunt_2,V_bus_2);
io_write(io_serial, str_data, STR_DATA_SIZE);
while(!usart_sync_is_tx_empty(io_serial)){}
//note: when string is shorter than previous one, the end still contains \n and values, so reset string in memory
memset(str_data, 0, STR_DATA_SIZE);
}
}
void retrieve_voltage(uint8_t address, uint16_t *V_bus, int16_t *V_shunt){
i2c_m_sync_set_slaveaddr(&I2C_0, address, I2C_M_SEVEN);
i2c_m_sync_cmd_read(&I2C_0, INA219_REG_SHUNTVOLTAGE, V_shunt, 2);
delay_us(1); // otherwise bytes are not correctly registered
*V_shunt = ((((*V_shunt & 0x00FF) << 8) | ((*V_shunt & 0xFF00) >> 8)) & INA219_VSHUNT_PGA1_MASK) *10; // put MSByte first and convert to µV
i2c_m_sync_cmd_read(&I2C_0, INA219_REG_BUSVOLTAGE, V_bus, 2);
delay_us(1);
*V_bus = ((((*V_bus & 0x00FF) << 8) | ((*V_bus & 0xFF00) >> 8)) >> 3) *4; // put MSByte first and convert to mV
}
- When using GPIO remember that pins are grouped into ports, which allows steering multiple pins at a time
- You can configure pins, clock, libraries etc in Atmel Start. You also have a device programming tool in Atmel Studio
- Do not use sleep(), as the board is now waiting for an interrupt. Use delay_ms() instead, unless you are using an RTOS which includes its own delay and sleep functionalities.
- Don’t forget to connect all grounds for the same reference between the board, sensors, etc.
- The debugging features of Atmel Studio can be used instead of having to print to the terminal via the usart gate. You can set breakpoints and give them a message that is displayed in the Studio output every time the software arrives at this point. Variables can be printed via {x}. However, when variables are still in use by the software when trying to print, this will result in . This means that the variable storage has been 'optimized out' by the software. This can be prevented by disabling optimisation in the Studio settings. Additionally, you might have to include a delay between changing and getting a variable to give the board time to update its memory.