-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tests: Added Timer integration tests and fixed SRS traces in Watchdog…
… test
- Loading branch information
1 parent
b78eec0
commit 45c5074
Showing
8 changed files
with
399 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.