Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ROS#138 - Implementation of get_system_time and an example #34

Merged
merged 5 commits into from
Aug 21, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pac::tc0::tc_channel::cmr_waveform_mode::{
};

/// Structure representing waveform mode configuration.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub struct WaveformModeConfig {
/// RC Compare event effect on timer's counter state.
pub rc_compare_effect: RcCompareEffect,
Expand Down
2 changes: 2 additions & 0 deletions arch/cortex-m/samv71-hal/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ pub enum HalError {
HalAlreadyCreated,
/// Error indicating that HAL has already been configured.
HalAlreadyConfigured,
/// Error indicating that the requested operation was called before HAL initialization.
HalNotInitializedYet,
}
170 changes: 167 additions & 3 deletions arch/cortex-m/samv71-hal/src/hal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ use aerugo_cortex_m::Mutex;
use aerugo_hal::system_hal::{SystemHal, SystemHardwareConfig};
use bare_metal::CriticalSection;

use cortex_m::asm;

use crate::drivers::timer::channel_config::ChannelClock;
use crate::drivers::timer::timer_config::{ExternalClock, ExternalClockSource};
use crate::drivers::timer::waveform_config::{
ComparisonEffect, OutputSignalEffects, WaveformModeConfig,
};
use crate::drivers::timer::{Ch0, Ch1, Ch2, Channel, Timer, Waveform};
use crate::drivers::watchdog::watchdog_config::WatchdogConfig;
use crate::drivers::watchdog::Watchdog;
use crate::error::HalError;
use crate::system_peripherals::SystemPeripherals;
use crate::user_peripherals::UserPeripherals;
use internal_cell::InternalCell;
use pac;
use pac::{self, PMC, TC0};

/// This lock will prevent from creating HAL instance twice in the system.
/// Since HAL manages the access to peripherals, creating and using multiple
Expand Down Expand Up @@ -66,14 +74,19 @@ impl Hal {

let system_peripherals = SystemPeripherals {
watchdog: Watchdog::new(mcu_peripherals.WDT),
timer: Timer::new(mcu_peripherals.TC0),
timer_ch0: None,
timer_ch1: None,
timer_ch2: None,
pmc: Some(mcu_peripherals.PMC),
};

let user_peripherals = UserPeripherals {
chip_id: Some(mcu_peripherals.CHIPID),
timer_counter1: Some(mcu_peripherals.TC1),
timer_counter2: Some(mcu_peripherals.TC2),
timer_counter3: Some(mcu_peripherals.TC3),
pmc: Some(mcu_peripherals.PMC),
pmc: None,
nvic: Some(core_peripherals.NVIC),
};

Expand Down Expand Up @@ -110,11 +123,56 @@ impl SystemHal for Hal {
Err(_) => return Err(HalError::HalAlreadyConfigured),
};

if let Some(pmc) = peripherals.pmc.take() {
let (ch0, ch1, ch2) = configure_timer_for_hal(&mut peripherals.timer, &pmc);

peripherals.timer_ch0.replace(ch0);
peripherals.timer_ch1.replace(ch1);
peripherals.timer_ch2.replace(ch2);

if let Some(user_peripherals) = self.user_peripherals.as_mut() {
user_peripherals.pmc.replace(pmc);
} else {
// That should never happen, as both system and user peripherals are created at
// the same time, but to prevent hard-to-detect issues in the future, this will
// throw an error anyway.
return Err(HalError::HalNotInitializedYet);
}
} else {
// If PMC is not there, it means that the system has already been initialized.
return Err(HalError::HalAlreadyConfigured);
}

// Start system timer
peripherals.timer.trigger_all_channels();

Ok(())
}

fn get_system_time(&self) -> Self::Instant {
crate::time::TimerInstantU64::from_ticks(0) // TODO: replace this stub with correct implementation
// SAFETY: This is safe, because this is a single-core system,
// and no other references to system peripherals should exist.
let peripherals = unsafe { self.system_peripherals.as_ref() };

let ch0 = peripherals
.timer_ch0
.as_ref()
.expect("get_system_time called before HAL initialization");
let ch1 = peripherals
.timer_ch1
.as_ref()
.expect("get_system_time called before HAL initialization");
let ch2 = peripherals
.timer_ch2
.as_ref()
.expect("get_system_time called before HAL initialization");

let time_ch0 = ch0.counter_value();
let time_ch1 = ch1.counter_value();
let time_ch2 = ch2.counter_value();

// Timer's clock is 1MHz, so returned value is in microseconds.
crate::time::TimerInstantU64::from_ticks(as_48bit_unsigned(time_ch0, time_ch1, time_ch2))
}

fn feed_watchdog(&mut self) {
Expand All @@ -140,3 +198,109 @@ impl SystemHal for Hal {
cortex_m::interrupt::free(f)
}
}

/// Type representing all TC0 channels in Waveform mode.
type Tc0Channels = (
Channel<TC0, Ch0, Waveform>,
Channel<TC0, Ch1, Waveform>,
Channel<TC0, Ch2, Waveform>,
);

/// Configures a timer for HAL usage.
///
/// This function configures Timer (using hardware TC0 instance) in Waveform mode with proper
/// input clocks (configured via PMC), and chains it's channels to achieve high-resolution
/// time source for the system.
///
/// Timer's source clock first goes into channel 0, which generates RC compare events that
/// toggle it's TIOA0 output, effectively dividing the input frequency by the value of RC register.
/// TIOA0 is connected via XC1 to channel 1, which does the same thing for TIOA1 output, which is
/// connected via XC2 to channel 2.
///
/// # Parameters
/// * `timer` - HAL Timer instance
/// * `pmc` - PAC PMC instance
fn configure_timer_for_hal(timer: &mut Timer<TC0>, pmc: &PMC) -> Tc0Channels {
configure_pmc_for_timer(pmc);

// If any of the configurations is not available, user cannot do anything about it and it
// certainly should not pass any tests, so just hard fault it.
timer
.configure_external_clock_source(ExternalClock::XC1, ExternalClockSource::TIOA0)
.expect("Cannot connect TIOA0 to XC1");
timer
.configure_external_clock_source(ExternalClock::XC2, ExternalClockSource::TIOA1)
.expect("Cannot connect TIOA1 to XC2");

// If any of the channels is not available, it's a hard fault as it's an internal bug in Aerugo
let ch0 = timer.channel_0.take().expect("TC0 CH0 already taken");
let ch1 = timer.channel_1.take().expect("TC0 CH1 already taken");
let ch2 = timer.channel_2.take().expect("TC0 CH2 already taken");

let waveform_config = WaveformModeConfig {
tioa_effects: OutputSignalEffects {
software_trigger: ComparisonEffect::Clear,
rc_comparison: ComparisonEffect::Toggle,
..Default::default()
},
..Default::default()
};

let ch0 = ch0.into_waveform_channel(waveform_config);
let ch1 = ch1.into_waveform_channel(waveform_config);
let ch2 = ch2.into_waveform_channel(waveform_config);

// Set RC values for all channels to max, so we can achieve full 48-bit resolution
ch0.set_rc(u16::MAX);
ch1.set_rc(u16::MAX);
ch2.set_rc(u16::MAX);

ch0.set_clock_source(ChannelClock::PmcPeripheralClock);
ch1.set_clock_source(ChannelClock::XC1);
ch2.set_clock_source(ChannelClock::XC2);

ch0.enable();
ch1.enable();
ch2.enable();

(ch0, ch1, ch2)
}

/// Configures PMC for TC0 operation with 3 chained channels
///
/// Enables TC0 CH0, CH1 and CH2 peripheral clocks, and configures PCK6
/// to generate proper clock for the timers.
///
/// PCK6 uses MAINCK clock source (which is 12MHz by default), and divides it by 12 to get
/// 1MHz input clock, used by the timer to achieve 1ns resolution.
///
fn configure_pmc_for_timer(pmc: &PMC) {
// Configure PCK6 for 1MHz TC0 output
// Source: MAINCK (12MHz by default)
// Divider: /6 (TODO: is there a hidden /2 prescaler somewhere?)
pmc.pck[6].write(|w| w.css().main_clk().pres().variant(5));

// Enable TC0 CH0, CH1 and CH2 peripheral clocks
pmc.pcer0
.write(|w| w.pid23().set_bit().pid24().set_bit().pid25().set_bit());

// Enable PCK6
pmc.scer.write(|w| w.pck6().set_bit());

// Wait until PCK6 is ready
while pmc.sr.read().pckrdy6().bit_is_clear() {
asm::nop();
}
}

/// Converts three 16-bit values into single 48-bit value.
///
/// Returns it as u64, shifted to left.
///
/// # Parameters
/// * `lsb` - Least significant bytes
/// * `mid` - Middle bytes
/// * `msb` - Most significant bytes
fn as_48bit_unsigned(lsb: u16, mid: u16, msb: u16) -> u64 {
((msb as u64) << 32) | ((mid as u64) << 16) | (lsb as u64)
}
17 changes: 16 additions & 1 deletion arch/cortex-m/samv71-hal/src/system_peripherals.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
//! Module representing peripherals internally used by Aerugo.

use crate::drivers::watchdog::Watchdog;
use pac::{PMC, TC0};

use crate::drivers::{
timer::{Ch0, Ch1, Ch2, Channel, Timer, Waveform},
watchdog::Watchdog,
};

/// System peripherals structure. These peripherals are represented as HAL drivers.
/// They are initialized on system init, and used directly by HAL to provide core functionality.
pub struct SystemPeripherals {
/// Watchdog instance.
pub watchdog: Watchdog,
/// Timer instance.
pub timer: Timer<TC0>,
/// Timer's channel 0 instance.
pub timer_ch0: Option<Channel<TC0, Ch0, Waveform>>,
/// Timer's channel 1 instance.
pub timer_ch1: Option<Channel<TC0, Ch1, Waveform>>,
/// Timer's channel 2 instance.
pub timer_ch2: Option<Channel<TC0, Ch2, Waveform>>,
/// PMC instance. This will be stored only temporarily here, between HAL init and system config
pub pmc: Option<PMC>,
}
80 changes: 51 additions & 29 deletions calldwell/rtt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@


class RTTClient:
"""Class acting as RTT front-end. Provides bidirectional communication with debugged program."""

class StreamMarker(IntEnum):
"""Enumeration listing Calldwell stream markers"""

Start = 0xDD
End = 0xEE
"""Class acting as RTT front-end. Provides buffered, bidirectional communication with
debugged program. Can also be used as a convenient base for custom protocols."""

def __init__(self, host: str, port: int, default_chunk_size: int = 1024) -> None:
"""Create instance of RTT client. Connects to RTT server via TCP socket.
Expand All @@ -33,7 +28,44 @@ def close(self) -> None:
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()

def receive_bytes(self) -> bytes:
def receive(self) -> bytes:
self._receive()
data = self._data_buffer.copy()
self._data_buffer.clear()
return data

def transmit(self, data: bytes) -> None:
self._transmit(data)

def receive_string(self) -> str:
return self.receive().decode("utf-8")

def transmit_string(self, data: str) -> None:
self.transmit(data.encode("utf-8"))

def _receive(self, chunk_size: Optional[int] = None) -> None:
"""Receives raw data from RTT target to internal buffer"""
if chunk_size is None:
chunk_size = self._default_chunk_size

received_bytes = self._socket.recv(chunk_size)
self._data_buffer.extend(received_bytes)

def _transmit(self, data: bytes) -> None:
"""Transmits raw data to RTT target."""
self._socket.send(data)


class CalldwellRTTClient(RTTClient):
"""Class providing bidirectional communication with program using Calldwell streams"""

class StreamMarker(IntEnum):
"""Enumeration listing Calldwell stream markers"""

Start = 0xDD
End = 0xEE

def receive_bytes_stream(self) -> bytes:
"""Receives data via Calldwell stream from RTT target"""
stream_data = self._extract_stream_data_from_recv_buffer()
while stream_data is None:
Expand All @@ -42,27 +74,29 @@ def receive_bytes(self) -> bytes:

return stream_data

def transmit_bytes(self, data: bytes) -> None:
def transmit_bytes_stream(self, data: bytes) -> None:
"""Transmits data via Calldwell stream to RTT target"""
self._transmit_stream_marker(RTTClient.StreamMarker.Start)
self._transmit_stream_marker(CalldwellRTTClient.StreamMarker.Start)
self._transmit(data)
self._transmit_stream_marker(RTTClient.StreamMarker.End)
self._transmit_stream_marker(CalldwellRTTClient.StreamMarker.End)

def receive_string(self) -> str:
def receive_string_stream(self) -> str:
"""Receives an UTF-8 string via Calldwell stream from RTT target"""
return self.receive_bytes().decode("utf-8")
return self.receive_bytes_stream().decode("utf-8")

def transmit_string(self, message: str) -> None:
def transmit_string_stream(self, message: str) -> None:
"""Transmits an UTF-8 string via Calldwell stream to RTT target"""
self.transmit_bytes(message.encode("utf-8"))
self.transmit_bytes_stream(message.encode("utf-8"))

def _extract_stream_data_from_recv_buffer(self) -> Optional[bytes]:
"""Looks for valid Calldwell stream in reception buffer, and returns it's data if found"""
start_marker_index = self._data_buffer.find(RTTClient.StreamMarker.Start)
start_marker_index = self._data_buffer.find(CalldwellRTTClient.StreamMarker.Start)
if start_marker_index == -1:
return None

end_marker_index = self._data_buffer.find(RTTClient.StreamMarker.End, start_marker_index)
end_marker_index = self._data_buffer.find(
CalldwellRTTClient.StreamMarker.End, start_marker_index
)
if end_marker_index == -1:
return None

Expand All @@ -79,15 +113,3 @@ def _extract_stream_data_from_recv_buffer(self) -> Optional[bytes]:
def _transmit_stream_marker(self, marker: StreamMarker) -> None:
# byteorder doesn't matter, but mypy asks for it
self._transmit(marker.to_bytes(length=1, signed=False, byteorder="big"))

def _receive(self, chunk_size: Optional[int] = None) -> None:
"""Receives raw data from RTT target to internal buffer"""
if chunk_size is None:
chunk_size = self._default_chunk_size

received_bytes = self._socket.recv(chunk_size)
self._data_buffer.extend(received_bytes)

def _transmit(self, data: bytes) -> None:
"""Transmits raw data to RTT target."""
self._socket.send(data)
Loading