Skip to content

Commit 06821a1

Browse files
authored
✨ (minor) Add console & steady clock (#12)
1 parent 3cd91d8 commit 06821a1

10 files changed

Lines changed: 556 additions & 13 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ libhal_test_and_make_library(
2121

2222
SOURCES
2323
src/serial.cpp
24+
src/console.cpp
25+
src/steady_clock.cpp
2426

2527
TEST_SOURCES
2628
tests/main.test.cpp

conanfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class libhal_mac_conan(ConanFile):
3535
def requirements(self):
3636
# Replace with appropriate processor library
3737
self.requires("libhal/[^4.12.0]", transitive_headers=True)
38-
self.requires("libhal-util/[^5.2.0]", transitive_headers=True)
38+
self.requires("libhal-util/[^5.5.0]", transitive_headers=True)
3939

4040
def package_info(self):
4141
self.cpp_info.set_property("cmake_target_name", "libhal::mac")

include/libhal-mac/console.hpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <atomic>
18+
#include <memory_resource>
19+
#include <span>
20+
#include <thread>
21+
#include <vector>
22+
23+
#include <libhal/pointers.hpp>
24+
#include <libhal/serial.hpp>
25+
#include <libhal/units.hpp>
26+
27+
namespace hal::mac::inline v1 {
28+
/**
29+
* @brief Serial communication interface using macOS console (stdin/stdout)
30+
*
31+
* Provides a way to simulate serial communication on macOS by redirecting
32+
* serial I/O operations to the console. This is particularly useful for
33+
* testing serial-based communication protocols and debugging embedded
34+
* applications that rely on serial interfaces.
35+
*
36+
* The implementation uses a background thread to continuously read from stdin
37+
* and store data in a circular buffer, while write operations are sent
38+
* directly to stdout.
39+
*/
40+
class console_serial : public hal::v5::serial
41+
{
42+
public:
43+
/**
44+
* @brief Create a console serial instance
45+
*
46+
* @param p_allocator Memory allocator for internal buffer management
47+
* @param p_buffer_size Size of the internal circular receive buffer (min: 32)
48+
* @return A strong_ptr to the created console_serial instance
49+
*/
50+
[[nodiscard]] static hal::v5::strong_ptr<console_serial> create(
51+
std::pmr::polymorphic_allocator<> p_allocator,
52+
hal::usize p_buffer_size);
53+
54+
/**
55+
* @brief Public constructor - but use create() instead
56+
*/
57+
console_serial(hal::v5::strong_ptr_only_token,
58+
std::pmr::polymorphic_allocator<> p_allocator,
59+
hal::usize p_buffer_size);
60+
61+
/**
62+
* @brief Destroy the console serial object
63+
*
64+
* Stops the background receive thread and waits for it to complete before
65+
* destruction.
66+
*/
67+
~console_serial() override;
68+
69+
// Non-copyable and non-movable
70+
console_serial(console_serial const&) = delete;
71+
console_serial& operator=(console_serial const&) = delete;
72+
console_serial(console_serial&&) = delete;
73+
console_serial& operator=(console_serial&&) = delete;
74+
75+
private:
76+
void driver_configure(hal::v5::serial::settings const& p_settings) override;
77+
void driver_write(std::span<hal::byte const> p_data) override;
78+
std::span<hal::byte const> driver_receive_buffer() override;
79+
hal::usize driver_cursor() override;
80+
81+
/**
82+
* @brief Background thread function for reading from stdin
83+
*/
84+
void receive_thread_function();
85+
86+
/// Memory allocator for buffer management
87+
std::pmr::polymorphic_allocator<> m_allocator;
88+
/// Circular buffer for storing received data from stdin
89+
std::pmr::vector<hal::byte> m_receive_buffer;
90+
/// Atomic cursor position for thread-safe buffer access
91+
std::atomic<hal::usize> m_receive_cursor{ 0 };
92+
/// Atomic flag to signal thread termination
93+
std::atomic<bool> m_stop_thread{ false };
94+
/// Background thread for reading from stdin
95+
std::thread m_receive_thread;
96+
};
97+
} // namespace hal::mac::inline v1

include/libhal-mac/serial.hpp

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,15 @@ class serial
111111
/**
112112
* @brief Set the DTR (Data Terminal Ready) signal state
113113
*
114-
<<<<<<< HEAD
115-
* @param p_state true to assert DTR (set high), false to deassert (set low)
116-
=======
117-
* @param p_state true to assert DTR (set high), false to de-assert (set
118-
low)
119-
>>>>>>> 520fe79 (:tada: First commit w/ serial impl (#1))
114+
* @param p_state true to assert DTR (set high), false to de-assert (set low)
120115
* @throws hal::operation_not_permitted if the operation fails
121116
*/
122117
void set_dtr(bool p_state);
123118

124119
/**
125120
* @brief Set the RTS (Request To Send) signal state
126121
*
127-
<<<<<<< HEAD
128-
* @param p_state true to assert RTS (set high), false to deassert (set low)
129-
=======
130-
* @param p_state true to assert RTS (set high), false to de-assert (set
131-
low)
132-
>>>>>>> 520fe79 (:tada: First commit w/ serial impl (#1))
122+
* @param p_state true to assert RTS (set high), false to de-assert (set low)
133123
* @throws hal::operation_not_permitted if the operation fails
134124
*/
135125
void set_rts(bool p_state);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <chrono>
18+
#include <memory_resource>
19+
20+
#include <libhal/pointers.hpp>
21+
#include <libhal/steady_clock.hpp>
22+
#include <libhal/units.hpp>
23+
24+
namespace hal::mac::inline v1 {
25+
/**
26+
* @brief Steady clock implementation using std::chrono::steady_clock for v5
27+
* interface
28+
*
29+
* Provides a high-resolution, monotonic time source for embedded applications
30+
* running on macOS. The clock is guaranteed to never go backwards, making it
31+
* suitable for measuring time intervals, implementing timeouts, and scheduling
32+
* periodic tasks.
33+
*
34+
* The uptime is measured from the time of object construction, and the
35+
* frequency is derived from std::chrono::steady_clock's period.
36+
*/
37+
class steady_clock
38+
: public hal::v5::steady_clock
39+
, public hal::v5::enable_strong_from_this<steady_clock>
40+
{
41+
public:
42+
/**
43+
* @brief Create a steady clock instance
44+
*
45+
* @param p_allocator Memory allocator (unused but follows libhal patterns)
46+
* @return A strong_ptr to the created steady_clock instance
47+
*/
48+
[[nodiscard]] static hal::v5::strong_ptr<steady_clock> create(
49+
std::pmr::polymorphic_allocator<> p_allocator);
50+
51+
/**
52+
* @brief Public constructor - but use create() instead
53+
*/
54+
steady_clock(hal::v5::strong_ptr_only_token);
55+
56+
private:
57+
hal::v5::hertz driver_frequency() override;
58+
hal::u64 driver_uptime() override;
59+
60+
/// Reference time point from construction for uptime calculations
61+
std::chrono::steady_clock::time_point m_start_time;
62+
};
63+
64+
/**
65+
* @brief Steady clock implementation using std::chrono::steady_clock for legacy
66+
* interface
67+
*
68+
* Provides the same functionality as steady_clock but implements the
69+
* legacy hal::steady_clock interface for backward compatibility with older
70+
* libhal code that hasn't been updated to the v5 API.
71+
*/
72+
class legacy_steady_clock
73+
: public hal::steady_clock
74+
, public hal::v5::enable_strong_from_this<legacy_steady_clock>
75+
{
76+
public:
77+
/**
78+
* @brief Create a legacy steady clock instance
79+
*
80+
* @param p_allocator Memory allocator (unused but follows libhal patterns)
81+
* @return A strong_ptr to the created legacy_steady_clock instance
82+
*/
83+
[[nodiscard]] static hal::v5::strong_ptr<legacy_steady_clock> create(
84+
std::pmr::polymorphic_allocator<> p_allocator);
85+
86+
/**
87+
* @brief Public constructor - but use create() instead
88+
*/
89+
legacy_steady_clock(hal::v5::strong_ptr_only_token);
90+
91+
private:
92+
hal::hertz driver_frequency() override;
93+
hal::u64 driver_uptime() override;
94+
95+
/// Reference time point from construction for uptime calculations
96+
std::chrono::steady_clock::time_point m_start_time;
97+
};
98+
} // namespace hal::mac::inline v1

src/console.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <libhal-mac/console.hpp>
16+
17+
#include <chrono>
18+
#include <cstdio>
19+
20+
#include <libhal/error.hpp>
21+
22+
namespace hal::mac::inline v1 {
23+
24+
hal::v5::strong_ptr<console_serial> console_serial::create(
25+
std::pmr::polymorphic_allocator<> p_allocator,
26+
hal::usize p_buffer_size)
27+
{
28+
if (p_buffer_size < 32) {
29+
p_buffer_size = 32;
30+
}
31+
32+
return hal::v5::make_strong_ptr<console_serial>(
33+
p_allocator, p_allocator, p_buffer_size);
34+
}
35+
36+
console_serial::console_serial(hal::v5::strong_ptr_only_token,
37+
std::pmr::polymorphic_allocator<> p_allocator,
38+
hal::usize p_buffer_size)
39+
: m_allocator(p_allocator)
40+
, m_receive_buffer(p_buffer_size, hal::byte{ 0 }, p_allocator)
41+
, m_receive_thread(&console_serial::receive_thread_function, this)
42+
{
43+
}
44+
45+
console_serial::~console_serial()
46+
{
47+
m_stop_thread.store(true, std::memory_order_release);
48+
if (m_receive_thread.joinable()) {
49+
m_receive_thread.join();
50+
}
51+
}
52+
53+
void console_serial::driver_configure(
54+
hal::v5::serial::settings const& p_settings)
55+
{
56+
// Console doesn't need configuration - settings are ignored
57+
static_cast<void>(p_settings);
58+
}
59+
60+
void console_serial::driver_write(std::span<hal::byte const> p_data)
61+
{
62+
// Use fwrite to stdout for binary safety
63+
std::fwrite(p_data.data(), 1, p_data.size(), stdout);
64+
std::fflush(stdout);
65+
}
66+
67+
std::span<hal::byte const> console_serial::driver_receive_buffer()
68+
{
69+
return m_receive_buffer;
70+
}
71+
72+
hal::usize console_serial::driver_cursor()
73+
{
74+
return m_receive_cursor.load(std::memory_order_acquire);
75+
}
76+
77+
void console_serial::receive_thread_function()
78+
{
79+
while (!m_stop_thread.load(std::memory_order_acquire)) {
80+
int ch = std::getchar();
81+
82+
if (ch != EOF) {
83+
auto current_cursor = m_receive_cursor.load(std::memory_order_acquire);
84+
m_receive_buffer[current_cursor] = static_cast<hal::byte>(ch);
85+
86+
auto new_cursor = (current_cursor + 1) % m_receive_buffer.size();
87+
m_receive_cursor.store(new_cursor, std::memory_order_release);
88+
} else {
89+
// No data available, sleep briefly to avoid busy waiting
90+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
91+
}
92+
}
93+
}
94+
95+
} // namespace hal::mac::inline v1

0 commit comments

Comments
 (0)