Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/tests/fram/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Makefile for FRAM test application

# Specify this directory relative to the current application.
TOCK_USERLAND_BASE_DIR = ../../..

# Which files to compile.
C_SRCS := main.c mb85rc1mt.c unity.c

STACK_SIZE := 2048

# Include userland master makefile. Contains rules and flags for actually
# building the application.

# Disable floating point support in Unity to avoid -Wfloat-equal warnings
# which cause CI failures due to -Werror.
override CFLAGS += -DUNITY_EXCLUDE_FLOAT -DUNITY_EXCLUDE_DOUBLE

include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk
58 changes: 58 additions & 0 deletions examples/tests/fram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
FRAM Test (MB85RC1MT)
=====================

This application tests the MB85RC1MT FRAM driver over I2C on the ENTS node
running TockOS.

## Hardware Requirements

- ENTS node board with MB85RC1MT FRAM chip connected via I2C
- TockOS kernel with I2C master driver enabled

## Building

```bash
cd examples/tests/fram
make
```

## Flashing

```bash
tockloader install build/fram.tab
```

## Expected Output

```
========================================
ENTS FRAM Test (MB85RC1MT via TockOS)
========================================

FRAM size: 131072 bytes

[PASS] fram_size() == 131072
[PASS] fram_write valid data
[PASS] fram_write zero length
[PASS] fram_write out of range
[PASS] fram_read status OK
[PASS] fram_read zero length
[PASS] fram_read out of range
[PASS] fram_read matches written data
[PASS] multi-address: pattern A
[PASS] multi-address: pattern B

========================================
Results: 10 passed, 0 failed
========================================
ALL TESTS PASSED
```

## Notes

- Ported from `stm32/test/test_fram/test_fram.c` in the ENTS-node-firmware
baremetal codebase.
- The driver uses byte-at-a-time I2C writes for correctness across address
boundaries. Future optimization could use bulk writes within a single page.
- The `HAL_I2C_Mem_Write` / `HAL_I2C_Mem_Read` calls were replaced with
`i2c_master_write_sync` / `i2c_master_write_read_sync` from libtock-c.
154 changes: 154 additions & 0 deletions examples/tests/fram/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* @file main.c
* @brief Test application for the MB85RC1MT FRAM driver on TockOS
*
* Exercises basic FRAM read/write operations and boundary checks
* using the Unity testing framework.
*
* Based on stm32/test/test_fram/test_fram.c from ENTS-node-firmware.
*
* @author Pritish Mahato
* @date 2026-02-25
*
* Copyright (c) 2026 jLab, UCSC
*/

#include <stdio.h>
#include <string.h>

#include "mb85rc1mt.h"
#include "unity.h"

// ---------------------------------------------------------------------------
// Forward declarations for test functions.
// Required by -Wmissing-prototypes and -Wmissing-declarations.
// ---------------------------------------------------------------------------

void test_fram_size(void);
void test_write_valid_data(void);
void test_write_out_of_range(void);
void test_write_zero_length(void);
void test_read_valid_data(void);
void test_read_out_of_range(void);
void test_read_zero_length(void);
void test_read_status(void);
void test_write_read_multiple_addresses(void);

// ---------------------------------------------------------------------------
// Unity required callbacks (declared in unity.h, defined here).
// ---------------------------------------------------------------------------

void setUp(void) {
}

void tearDown(void) {
}

// ---------------------------------------------------------------------------
// Test Cases (ported from stm32/test/test_fram/test_fram.c)
// ---------------------------------------------------------------------------

void test_fram_size(void) {
TEST_ASSERT_EQUAL_UINT32(131072, fram_size());
}

void test_write_valid_data(void) {
uint8_t data[] = {1, 2, 3, 4, 5};
FramStatus status = fram_write(0x00, data, sizeof(data));
TEST_ASSERT_EQUAL_INT(FRAM_OK, status);
}

void test_write_out_of_range(void) {
uint8_t data[] = {1, 2, 3, 4, 5};
FramAddr addr = fram_size() + 1;
FramStatus status = fram_write(addr, data, sizeof(data));
TEST_ASSERT_EQUAL_INT(FRAM_OUT_OF_RANGE, status);
}

void test_write_zero_length(void) {
uint8_t data[] = {1, 2, 3, 4, 5};
FramStatus status = fram_write(0x00, data, 0);
TEST_ASSERT_EQUAL_INT(FRAM_OK, status);
}

void test_read_valid_data(void) {
uint8_t write_data[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x42};
FramAddr addr = 0x100;

FramStatus status = fram_write(addr, write_data, sizeof(write_data));
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "write failed");

uint8_t read_data[5] = {0};
status = fram_read(addr, sizeof(read_data), read_data);
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "read failed");

TEST_ASSERT_EQUAL_UINT8_ARRAY(write_data, read_data, sizeof(write_data));
}

void test_read_out_of_range(void) {
uint8_t data[5];
FramAddr addr = fram_size() + 1;
FramStatus status = fram_read(addr, sizeof(data), data);
TEST_ASSERT_EQUAL_INT(FRAM_OUT_OF_RANGE, status);
}

void test_read_zero_length(void) {
uint8_t data[5];
FramStatus status = fram_read(0x00, 0, data);
TEST_ASSERT_EQUAL_INT(FRAM_OK, status);
}

void test_read_status(void) {
uint8_t data[5];
FramStatus status = fram_read(0x00, sizeof(data), data);
TEST_ASSERT_EQUAL_INT(FRAM_OK, status);
}

void test_write_read_multiple_addresses(void) {
uint8_t pattern_a[] = {0xAA, 0xBB, 0xCC};
uint8_t pattern_b[] = {0x11, 0x22, 0x33};
FramAddr addr_a = 0x200;
FramAddr addr_b = 0x300;

FramStatus status;

status = fram_write(addr_a, pattern_a, sizeof(pattern_a));
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "write A failed");

status = fram_write(addr_b, pattern_b, sizeof(pattern_b));
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "write B failed");

uint8_t read_a[3] = {0};
uint8_t read_b[3] = {0};

status = fram_read(addr_a, sizeof(read_a), read_a);
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "read A failed");

status = fram_read(addr_b, sizeof(read_b), read_b);
TEST_ASSERT_EQUAL_INT_MESSAGE(FRAM_OK, status, "read B failed");

TEST_ASSERT_EQUAL_UINT8_ARRAY(pattern_a, read_a, 3);
TEST_ASSERT_EQUAL_UINT8_ARRAY(pattern_b, read_b, 3);
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

int main(void) {
printf("\ntock$ ENTS FRAM Test (MB85RC1MT via TockOS)\n\n");

UNITY_BEGIN();

RUN_TEST(test_fram_size);
RUN_TEST(test_write_valid_data);
RUN_TEST(test_write_zero_length);
RUN_TEST(test_write_out_of_range);
RUN_TEST(test_read_status);
RUN_TEST(test_read_zero_length);
RUN_TEST(test_read_out_of_range);
RUN_TEST(test_read_valid_data);
RUN_TEST(test_write_read_multiple_addresses);

return UNITY_END();
}
123 changes: 123 additions & 0 deletions examples/tests/fram/mb85rc1mt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* @file mb85rc1mt.c
* @brief FRAM driver for MB85RC1MT using TockOS I2C master syscalls
*
* Ported from stm32/lib/storage/src/mb85rc1mt.c.
* Replaces HAL_I2C_Mem_Write/Read with libtock-c i2c_master_write_sync
* and i2c_master_write_read_sync.
*
* The MB85RC1MT is a 128KB (1 Mbit) FRAM with I2C interface.
* Memory is accessed via a 17-bit address:
* - Bit 16 is encoded in the device address (A0 bit position)
* - Bits 15:0 are sent as a 16-bit memory address
*
* @author Pritish Mahato
* @date 2026-02-25
*
* Copyright (c) 2026 jLab, UCSC
*/

#include <libtock/peripherals/i2c_master.h>

#include "mb85rc1mt.h"

/** Base I2C address of the MB85RC1MT (8-bit format: 0xA0) */
#define MB85RC1MT_BASE_ADDR 0xA0

/** Total size of the chip in bytes (128 KB = 1 Mbit) */
#define MB85RC1MT_SIZE (1 << 17)

/** I2C buffer size: 2 bytes address + 1 byte data (for byte-at-a-time writes) */
#define I2C_BUF_SIZE 3

/**
* @brief Internal representation of the MB85RC1MT I2C address
*/
typedef struct {
/** 8-bit device address (includes upper address bit and R/W) */
uint8_t dev;
/** 16-bit memory address within the device */
uint16_t mem;
} Mb85rc1mtAddress;

/**
* @brief Convert a flat FRAM address to MB85RC1MT I2C address format
*
* The 17-bit address is split:
* - Bit 16 -> device address bit A0 (shifted into position)
* - Bits 15:0 -> 16-bit memory address
*
* @param addr Flat FRAM address (0 to MB85RC1MT_SIZE-1)
* @return Formatted I2C address
*/
static Mb85rc1mtAddress convert_address(FramAddr addr) {
Mb85rc1mtAddress i2c_addr;

// Upper address bits go into device address.
// Shift right by 15 to get bit 16 into position, mask to keep only
// relevant bits, OR with base address.
i2c_addr.dev = MB85RC1MT_BASE_ADDR | ((addr >> 15) & 0x0E);
i2c_addr.mem = addr & 0xFFFF;

return i2c_addr;
}

FramStatus fram_write(FramAddr addr, const uint8_t* data, size_t len) {
// Bounds check
if (addr + len > MB85RC1MT_SIZE) {
return FRAM_OUT_OF_RANGE;
}

// Write data byte-by-byte.
// The MB85RC1MT supports sequential writes within a page, but for
// simplicity and correctness across page boundaries we write one byte
// at a time, matching the baremetal implementation.
for (size_t i = 0; i < len; i++) {
Mb85rc1mtAddress i2c_addr = convert_address(addr + i);

// Build I2C write buffer: [mem_addr_hi, mem_addr_lo, data_byte]
uint8_t buf[I2C_BUF_SIZE];
buf[0] = (uint8_t)(i2c_addr.mem >> 8);
buf[1] = (uint8_t)(i2c_addr.mem & 0xFF);
buf[2] = data[i];

int ret = i2c_master_write_sync(i2c_addr.dev, buf, I2C_BUF_SIZE);
if (ret != RETURNCODE_SUCCESS) {
return FRAM_ERROR;
}
}

return FRAM_OK;
}

FramStatus fram_read(FramAddr addr, size_t len, uint8_t* data) {
// Bounds check
if (addr + len > MB85RC1MT_SIZE) {
return FRAM_OUT_OF_RANGE;
}

// Read data byte-by-byte to handle address boundary crossings correctly.
for (size_t i = 0; i < len; i++) {
Mb85rc1mtAddress i2c_addr = convert_address(addr + i);

// Build buffer with memory address for the write phase of write-read.
uint8_t buf[2];
buf[0] = (uint8_t)(i2c_addr.mem >> 8);
buf[1] = (uint8_t)(i2c_addr.mem & 0xFF);

// write_read: write 2 bytes (memory address), then read 1 byte (data).
// After completion, the read byte is placed at buf[0].
int ret = i2c_master_write_read_sync(i2c_addr.dev, buf, 2, 1);
if (ret != RETURNCODE_SUCCESS) {
return FRAM_ERROR;
}

data[i] = buf[0];
}

return FRAM_OK;
}

FramAddr fram_size(void) {
return MB85RC1MT_SIZE;
}
Loading
Loading