Skip to content

Commit

Permalink
Tests: Added Timer integration tests and fixed SRS traces in Watchdog…
Browse files Browse the repository at this point in the history
… test
  • Loading branch information
SteelPh0enix committed Aug 16, 2023
1 parent b78eec0 commit 45c5074
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 2 deletions.
11 changes: 11 additions & 0 deletions testbins/test-hal-timer/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[build]
target = "thumbv7em-none-eabihf"

[env]
AERUGO_TASKLET_COUNT = { value = "2" }

[target.thumbv7em-none-eabihf]
rustflags = [
"-C", "link-arg=--nmagic", # Disable page alignment of sections (to prevent issues with binary size)
"-C", "link-arg=-Tlink.x", # Use cortex-m-rt's linker script
]
23 changes: 23 additions & 0 deletions testbins/test-hal-timer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "test-hal-timer"
authors = ["Wojciech Olech <wojciech_olech@hotmail.com>"]
edition = "2021"
version = "0.1.0"

[dependencies]
aerugo = { version = "0.1.0", path = "../..", features = [
"use-aerugo-cortex-m",
"rt",
] }
calldwell = { version = "0.1.0", path = "../../calldwell/calldwell-rs" }
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = { version = "0.7.3", features = ["device"] }
panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] }

[features]
rt = ["aerugo/rt"]

[profile.release]
codegen-units = 1
lto = true
debug = true
14 changes: 14 additions & 0 deletions testbins/test-hal-timer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Put the linker script somewhere the linker can find it
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
}
6 changes: 6 additions & 0 deletions testbins/test-hal-timer/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Linker script for SAMV71Q21 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x00400000, LENGTH = 0x00200000
RAM (rwx) : ORIGIN = 0x20400000, LENGTH = 0x00060000
}
212 changes: 212 additions & 0 deletions testbins/test-hal-timer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#![no_std]
#![no_main]

extern crate aerugo;
extern crate calldwell;
extern crate cortex_m;
extern crate cortex_m_rt as rt;
extern crate panic_rtt_target;

use aerugo::{
hal::drivers::timer::{
channel_config::ChannelClock, waveform_config::WaveformModeConfig, Ch0, Channel, Timer,
Waveform, TC1,
},
hal::{drivers::timer::channel_config::ChannelInterrupts, interrupt, NVIC, PMC},
time::MillisDurationU32,
InitApi, RuntimeApi, SystemHardwareConfig, TaskletConfig, TaskletStorage, AERUGO,
};
use calldwell::{with_rtt_in, with_rtt_out};
use core::{cell::RefCell, fmt::Write, ops::AddAssign};
use cortex_m::interrupt::{free as irq_free, Mutex};
use rt::entry;

// Test scenario:
// - Configure Timer to use non-default clock source
// - Enable timer's interrupt, and count it's overflows
// - Enable and start timer's clock
// - Check if IRQ count is increasing via tasklet
// - Stop and disable the timer
// - Check if IRQ count stopped increasing via tasklet
// - Change timer's clock source, enable and start it
// - Check if IRQ rate changed compared to previous check

static TIMER_CHANNEL: Mutex<RefCell<Option<Channel<TC1, Ch0, Waveform>>>> =
Mutex::new(RefCell::new(None));
static TIMER_IRQ_COUNT: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));

#[derive(Default)]
struct TimerTestTaskContext {
acc: u32,
}

fn timer_test_task(_: (), context: &mut TimerTestTaskContext, _: &dyn RuntimeApi) {
context.acc = context.acc.wrapping_add(1);

if context.acc % 100 == 0 {
let irq_count = get_irq_count();
with_rtt_out(|w, _| write!(w.writer(), "{}", irq_count).unwrap());

if context.acc == 1000 {
disable_channel();
}

if context.acc == 2000 {
change_channels_clock_source();
enable_and_trigger_channel();
}
}
}

static TIMER_TEST_TASK_STORAGE: TaskletStorage<(), TimerTestTaskContext, 0> = TaskletStorage::new();

fn initialize_tasks() {
let timer_test_task_config = TaskletConfig {
name: "TimerTestTask",
..Default::default()
};

let timer_test_task_context = TimerTestTaskContext::default();

AERUGO
.create_tasklet_with_context(
timer_test_task_config,
timer_test_task,
timer_test_task_context,
&TIMER_TEST_TASK_STORAGE,
)
.expect("Unable to create timer test task!");

let timer_test_task_handle = TIMER_TEST_TASK_STORAGE
.create_handle()
.expect("Unable to create timer test task handle!");

AERUGO
.subscribe_tasklet_to_cyclic(&timer_test_task_handle, None)
.expect("Unable to subscribe timer test task to cyclic execution!");
}

fn initialize_nvic() {
unsafe {
// Enable TC0 CH0 interrupt
NVIC::unmask(interrupt::TC3);
}
}

fn initialize_pmc(pmc: PMC) {
// Enable TC1 CH0 clock
pmc.pcer0.write(|w| w.pid26().set_bit());
}

fn initialize_timer(mut timer: Timer<TC1>) {
// Enable waveform mode
let channel = timer
.channel_0
.take()
.expect("TC1 Ch0 already taken")
.into_waveform_channel(WaveformModeConfig::default());

// Use non-default clock source
channel.set_clock_source(ChannelClock::MckDividedBy8);

// Enable overflow interrupt
channel.enable_interrupts(ChannelInterrupts {
counter_overflow: true,
..Default::default()
});

// Put channel's instance in global context
irq_free(|cs| {
TIMER_CHANNEL.borrow(cs).replace(Some(channel));
});
}

fn enable_and_trigger_channel() {
irq_free(|cs| {
let channel_ref = TIMER_CHANNEL.borrow(cs).borrow();
let channel = channel_ref.as_ref().unwrap();
channel.enable();
channel.trigger();
});
}

fn disable_channel() {
irq_free(|cs| {
let channel_ref = TIMER_CHANNEL.borrow(cs).borrow();
let channel = channel_ref.as_ref().unwrap();
channel.disable();
});
}

fn change_channels_clock_source() {
irq_free(|cs| {
let channel_ref = TIMER_CHANNEL.borrow(cs).borrow();
let channel = channel_ref.as_ref().unwrap();
channel.set_clock_source(ChannelClock::MckDividedBy32);
});
}

fn clear_channel_irq_flags() {
irq_free(|cs| {
let channel_ref = TIMER_CHANNEL.borrow(cs).borrow();
let channel = channel_ref.as_ref().unwrap();
channel.read_and_clear_status();
});
}

fn get_irq_count() -> u32 {
irq_free(|cs| *TIMER_IRQ_COUNT.borrow(cs).borrow())
}

#[entry]
fn main() -> ! {
calldwell::initialize();
wait_for_host();

AERUGO.initialize(SystemHardwareConfig {
watchdog_timeout: MillisDurationU32::secs(3),
});

let peripherals = AERUGO
.peripherals()
.expect("HAL was not initialized!")
.expect("Peripherals already taken!");

let timer = Timer::new(peripherals.timer_counter1.expect("TC1 already taken!"));

initialize_nvic();
initialize_pmc(peripherals.pmc.expect("PMC already taken!"));
initialize_timer(timer);

initialize_tasks();
enable_and_trigger_channel();

AERUGO.start();
}

fn wait_for_host() {
let mut input_buffer: [u8; 128] = [0; 128];

with_rtt_out(|w, _| w.write_str("mcu ready"));
let response_length = with_rtt_in(|r, _| r.read(&mut input_buffer));

if let Err(e) = response_length {
with_rtt_out(|w, _| {
write!(
w.writer(),
"an error occurred while receiving response from host: {:?}",
e
)
.expect("Unable to send data via RTT")
});
}
}

#[interrupt]
fn TC3() {
clear_channel_irq_flags();

irq_free(|cs| {
TIMER_IRQ_COUNT.borrow(cs).borrow_mut().add_assign(1);
});
}
103 changes: 103 additions & 0 deletions tests/requirements/test_hal_timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import os

import logging
from typing import List, Tuple
from calldwell.gdb_client import GDBClient
from calldwell.ssh_client import SSHClient
from calldwell.rtt_client import RTTClient

BOARD_LOGIN = str(os.environ.get("AERUGO_BOARD_LOGIN"))
BOARD_PASSWORD = str(os.environ.get("AERUGO_BOARD_PASSWORD"))
BOARD_HOSTNAME = str(os.environ.get("AERUGO_BOARD_HOSTNAME"))
BOARD_GDB_PORT = str(os.environ.get("AERUGO_BOARD_GDB_PORT"))
BOARD_RTT_PORT = str(os.environ.get("AERUGO_BOARD_RTT_PORT"))
GDB_EXECUTABLE = "arm-none-eabi-gdb"
TEST_BINARY_PATH = (
"./testbins/test-hal-timer/target/thumbv7em-none-eabihf/debug/test-hal-timer"
)


def init_test() -> Tuple[GDBClient, RTTClient, SSHClient]:
ssh = SSHClient(BOARD_HOSTNAME, BOARD_LOGIN, BOARD_PASSWORD)
ssh.execute("./setup_debugging_sam_clean.sh")

gdb = GDBClient(GDB_EXECUTABLE, log_responses=False, log_execution=False)
gdb.connect_to_remote(f"{BOARD_HOSTNAME}:{BOARD_GDB_PORT}")
gdb.start_rtt_server(int(BOARD_RTT_PORT), 0)

rtt = RTTClient(BOARD_HOSTNAME, port=int(BOARD_RTT_PORT))

gdb.load_executable(TEST_BINARY_PATH)
rtt_symbol = gdb.get_variable("_SEGGER_RTT")
if rtt_symbol is None:
print("COULD NOT FIND RTT SECTION!")
exit(1)
gdb.start_program()
gdb.setup_rtt(rtt_symbol.address, 0x400, "SEGGER RTT")
gdb.set_breakpoint("calldwell::initialize")
gdb.continue_program()
gdb.wait_for_breakpoint_hit()
gdb.finish_function_execution()
gdb.start_rtt()
gdb.continue_program()

init_message = rtt.receive_stream().decode()
if init_message != "mcu ready":
print("TEST FAILED: MCU NOT READY")
exit(1)

rtt.transmit_stream("ok".encode())

return gdb, rtt, ssh


def finish_test(ssh: SSHClient):
ssh.execute("pkill openocd")
ssh.close()


def average_difference(values: List[int]) -> float:
diffs = [j - i for i, j in zip(values[:-1], values[1:])]
return sum(diffs) / len(diffs)


def main():
_, rtt, ssh = init_test()

# Timer should be running by default, and program should output
# it's overflows via RTT.

# First 10 messages should contain fast-changing timer IRQ count
fast_irq_counts: List[int] = list()
for _ in range(10):
fast_irq_counts.append(int(rtt.receive_stream().decode()))
avg_diffs_fast = average_difference(fast_irq_counts)

# After 10 messages, tasklet should disable the timer, so incoming IRQ counts
# should not change
stopped_irq_counts: List[int] = list()
for _ in range(10):
stopped_irq_counts.append(int(rtt.receive_stream().decode()))
avg_diffs_stopped = average_difference(stopped_irq_counts)

# After another 10 messages, tasklet should switch timer's source to slower one
# and enable it, returning IRQ count that's changing slower
slow_irq_counts: List[int] = list()
for _ in range(10):
slow_irq_counts.append(int(rtt.receive_stream().decode()))

avg_diffs_slow = average_difference(slow_irq_counts)

if avg_diffs_fast <= avg_diffs_slow:
print("TEST FAILED, FASTER CLOCK IS IN FACT SLOWER")
exit(2)

if avg_diffs_stopped != 0:
print("TEST FAILED, CLOCK DID NOT STOP")

finish_test(ssh)


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
Loading

0 comments on commit 45c5074

Please sign in to comment.