Skip to content

Commit eb2d381

Browse files
committed
✨ Add nunchuck driver, demo, and test
1 parent 2de1eec commit eb2d381

10 files changed

Lines changed: 296 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ libhal_test_and_make_library(
2020

2121
SOURCES
2222
src/input.cpp
23+
src/gamepad/nunchuck.cpp
2324

2425
TEST_SOURCES
26+
tests/gamepad/nunchuck.test.cpp
2527
tests/input.test.cpp
2628
tests/main.test.cpp
2729
)

demos/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ project(demos LANGUAGES CXX)
1919
libhal_build_demos(
2020
DEMOS
2121
input
22+
nunchuck
2223

2324
INCLUDES
2425
.

demos/applications/nunchuck.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Khalil Estell
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-input/gamepad/nunchuck.hpp>
16+
#include <libhal-util/serial.hpp>
17+
#include <libhal-util/steady_clock.hpp>
18+
#include <libhal/i2c.hpp>
19+
20+
#include <resource_list.hpp>
21+
22+
void application(resource_list& p_map)
23+
{
24+
using namespace std::chrono_literals;
25+
using namespace hal::literals;
26+
27+
auto& clock = *p_map.clock.value();
28+
auto& console = *p_map.console.value();
29+
30+
auto& i2c = *p_map.i2c.value();
31+
hal::input::nunchuck nunchuck(i2c);
32+
hal::print(console, "Demo Application Starting...\n\n");
33+
34+
while (true) {
35+
hal::delay(clock, 20ms);
36+
37+
auto data = nunchuck.read();
38+
// joystick data
39+
hal::print<32>(console, "JS-X:%d ", data.joystick_x());
40+
hal::print<32>(console, "JS-Y:%d ", data.joystick_y());
41+
// accelerometer data
42+
hal::print<32>(console, "ACL-X:%li ", data.accelerometer_x());
43+
hal::print<32>(console, "ACL-Y:%li ", data.accelerometer_y());
44+
hal::print<32>(console, "ACL-Z:%li ", data.accelerometer_z());
45+
// button data
46+
hal::print<32>(console, "Z-BTN:%d ", data.z_button());
47+
hal::print<32>(console, "C-BTN:%d\n", data.c_button());
48+
}
49+
}

demos/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ resource_list resources{};
2424
[[noreturn]] void terminate_handler() noexcept
2525
{
2626

27-
if (not resources.status_led && not resources.console) {
27+
if (not resources.status_led && not resources.clock) {
2828
// spin here until debugger is connected
2929
while (true) {
3030
continue;

demos/platforms/lpc4078.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <libhal-arm-mcu/lpc40/i2c.hpp>
1516
#include <libhal-armcortex/dwt_counter.hpp>
1617
#include <libhal-armcortex/startup.hpp>
1718
#include <libhal-armcortex/system_control.hpp>
@@ -21,14 +22,14 @@
2122
#include <libhal-lpc40/uart.hpp>
2223
#include <libhal-util/as_bytes.hpp>
2324

24-
#include "../resource_list.hpp"
25+
#include <resource_list.hpp>
2526

2627
resource_list initialize_platform()
2728
{
2829
using namespace hal::literals;
2930

3031
// Set the MCU to the maximum clock speed
31-
hal::lpc40::maximum(10.0_MHz);
32+
hal::lpc40::maximum(12.0_MHz);
3233

3334
// Create a hardware counter
3435
static hal::cortex_m::dwt_counter counter(
@@ -43,11 +44,13 @@ resource_list initialize_platform()
4344
});
4445

4546
static hal::lpc40::output_pin led(1, 10);
47+
static hal::lpc40::i2c i2c(2);
4648

4749
return {
4850
.reset = []() { hal::cortex_m::reset(); },
4951
.console = &uart0,
5052
.clock = &counter,
5153
.status_led = &led,
54+
.i2c = &i2c,
5255
};
5356
}

demos/platforms/stm32f103c8.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <libhal-armcortex/startup.hpp>
1919
#include <libhal-armcortex/system_control.hpp>
2020

21+
#include <libhal-soft/bit_bang_i2c.hpp>
2122
#include <libhal-stm32f1/clock.hpp>
2223
#include <libhal-stm32f1/constants.hpp>
2324
#include <libhal-stm32f1/output_pin.hpp>
@@ -42,11 +43,20 @@ resource_list initialize_platform()
4243
});
4344

4445
static hal::stm32f1::output_pin led('C', 13);
46+
static hal::stm32f1::output_pin sda('B', 7);
47+
static hal::stm32f1::output_pin scl('B', 6);
48+
// TODO(#4): Remove configure calls
49+
sda.configure({ .resistor = hal::pin_resistor::pull_up, .open_drain = true });
50+
scl.configure({ .resistor = hal::pin_resistor::pull_up, .open_drain = true });
51+
static hal::bit_bang_i2c::pins pins{ .sda = &sda, .scl = &scl };
52+
static hal::bit_bang_i2c bit_bang_i2c(pins, counter);
53+
bit_bang_i2c.configure(hal::i2c::settings{ .clock_rate = 100.0_kHz });
4554

4655
return {
4756
.reset = +[]() { hal::cortex_m::reset(); },
4857
.console = &uart1,
4958
.clock = &counter,
5059
.status_led = &led,
60+
.i2c = &bit_bang_i2c,
5161
};
5262
}

demos/resource_list.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@
1717
#include <optional>
1818

1919
#include <libhal/functional.hpp>
20+
#include <libhal/i2c.hpp>
2021
#include <libhal/output_pin.hpp>
2122
#include <libhal/serial.hpp>
2223
#include <libhal/steady_clock.hpp>
2324

2425
struct resource_list
2526
{
2627
hal::callback<void()> reset;
27-
std::optional<hal::serial*> console;
28-
std::optional<hal::steady_clock*> clock;
29-
std::optional<hal::output_pin*> status_led;
28+
std::optional<hal::serial*> console = std::nullopt;
29+
std::optional<hal::steady_clock*> clock = std::nullopt;
30+
std::optional<hal::output_pin*> status_led = std::nullopt;
3031
// Add more driver interfaces here ...
32+
std::optional<hal::i2c*> i2c = std::nullopt;
3133
};
3234

3335
// Application function is implemented by one of the .cpp files.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2024 Khalil Estell
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 <cstdint>
18+
19+
#include <libhal/i2c.hpp>
20+
21+
namespace hal::input {
22+
/**
23+
* @brief Driver for nunchuck i2c gamepad
24+
*
25+
*/
26+
class nunchuck
27+
{
28+
public:
29+
/**
30+
* @brief Holds information about gamepad state, and contains functions to
31+
* return translated data.
32+
*
33+
*/
34+
struct gamepad_data
35+
{
36+
/// @brief byte array containing data about gamepad state.
37+
std::array<hal::byte, 6> data;
38+
/**
39+
* @brief Get x-axis data of the joystick.
40+
* @return uint8_t representing the x-axis data of the joystick. Center
41+
* position is 128. 0-127 indicates the stick is pointed left and 129-255
42+
* indicates the stick is pointed right.
43+
*/
44+
std::uint8_t joystick_x();
45+
/**
46+
* @brief Get y-axis data of the joystick.
47+
* @return uint8_t representing the y-axis data of the joystick. Center
48+
* position is 128. 0-127 indicates the stick is pointed dwon and 129-255
49+
* indicates the stick is pointed up.
50+
*/
51+
std::uint8_t joystick_y();
52+
/**
53+
* @brief Get x-axis data of the accelerometer sensor.
54+
* @return uint16_t representing the x-axis data of the accelerometer.
55+
* Center position is about 512. Reducing values means left movement,
56+
* increasing values means right movement.
57+
*/
58+
std::uint16_t accelerometer_x();
59+
/**
60+
* @brief Get y-axis data of the accelerometer sensor.
61+
* @return uint16_t representing the y-axis data of the accelerometer.
62+
* Center position is about 512. Reducing values means backward movement,
63+
* increasing values means forward movement.
64+
*/
65+
std::uint16_t accelerometer_y();
66+
/**
67+
* @brief Get z-axis data of the accelerometer sensor.
68+
* @return uint16_t representing the z-axis data of the accelerometer.
69+
* Center position is about 512. Reducing values means downward movement,
70+
* increasing values means upward movement.
71+
*/
72+
std::uint16_t accelerometer_z();
73+
/**
74+
* @brief Get z button pressed state.
75+
* @return true representing if the z button is pressed
76+
* @return false representing if the z button is unpressed
77+
*/
78+
bool z_button();
79+
/**
80+
* @brief Get c button pressed state.
81+
* @return true representing if the c button is pressed
82+
* @return false representing if the c button is unpressed
83+
*/
84+
bool c_button();
85+
};
86+
87+
/**
88+
* @brief Create a nunchuck object
89+
* @param p_i2c - the driver for the i2c bus the nunchuck is connected to
90+
*/
91+
nunchuck(hal::i2c& p_i2c);
92+
gamepad_data read();
93+
94+
private:
95+
hal::i2c* m_i2c;
96+
};
97+
} // namespace hal::input

src/gamepad/nunchuck.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2024 Khalil Estell
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-input/gamepad/nunchuck.hpp>
16+
#include <libhal-util/i2c.hpp>
17+
18+
namespace hal::input {
19+
nunchuck::nunchuck(hal::i2c& p_i2c)
20+
: m_i2c(&p_i2c)
21+
{
22+
// handshake byte (0x40) is needed to initialize
23+
hal::write(
24+
*m_i2c, 0x52, std::array<hal::byte, 1>{ 0x40 }, hal::never_timeout());
25+
}
26+
27+
nunchuck::gamepad_data nunchuck::read()
28+
{
29+
gamepad_data controller{};
30+
hal::write_then_read(*m_i2c,
31+
0x52,
32+
std::array<hal::byte, 1>{ 0x00 },
33+
controller.data,
34+
hal::never_timeout());
35+
return controller;
36+
}
37+
38+
std::uint8_t nunchuck::gamepad_data::joystick_x()
39+
{
40+
return data[0];
41+
}
42+
43+
std::uint8_t nunchuck::gamepad_data::joystick_y()
44+
{
45+
return data[1];
46+
}
47+
48+
std::uint16_t nunchuck::gamepad_data::accelerometer_x()
49+
{
50+
// Accelerometer data is 10-bits split up with upper bits having a whole byte
51+
// to themselves and the lower 2 bits are contained in data[5].
52+
53+
// x-axis upper bits are in data[2]
54+
// Put the upper bits into a 16-bit int and left shift over 2 to make room for
55+
// lower bits.
56+
std::uint16_t x_upper_bits = data[2] << 2;
57+
// x-axis lower bits are bit 2 and 3 of data[5].
58+
// Used a bit mask with & operator to get only these 2 bits, then shift them
59+
// all the way to the right to be combined with upper bits.
60+
std::uint16_t x_lower_bits = (data[5] & 0b0000000000001100) >> 2;
61+
// Combine upper and lower bits by using an | operator
62+
return x_upper_bits | x_lower_bits;
63+
}
64+
65+
std::uint16_t nunchuck::gamepad_data::accelerometer_y()
66+
{
67+
// y-axis upper bits are in data[3]
68+
std::uint16_t y_upper_bits = data[3] << 2;
69+
// y-axis lower bits are bit 4 and 5 of data[5].
70+
std::uint16_t y_lower_bits = (data[5] & 0b0000000000110000) >> 4;
71+
return y_upper_bits | y_lower_bits;
72+
}
73+
74+
std::uint16_t nunchuck::gamepad_data::accelerometer_z()
75+
{
76+
// z-axis upper bits are in data[4]
77+
std::uint16_t z_upper_bits = data[4] << 2;
78+
// z-axis lower bits are bit 6 and 7 of data[5].
79+
std::uint16_t z_lower_bits = (data[5] & 0b0000000011000000) >> 6;
80+
return z_upper_bits | z_lower_bits;
81+
}
82+
83+
bool nunchuck::gamepad_data::z_button()
84+
{
85+
// z button state is bit 0 of data[5].
86+
// Originally returns 0 when pressed, inverted to return true when pressed.
87+
return !(data[5] & 0b00000001);
88+
}
89+
90+
bool nunchuck::gamepad_data::c_button()
91+
{
92+
// c button state is bit 1 of data[5].
93+
return !(data[5] & 0b0000010);
94+
}
95+
} // namespace hal::input

tests/gamepad/nunchuck.test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 Khalil Estell
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-input/gamepad/nunchuck.hpp>
16+
17+
#include <boost/ut.hpp>
18+
19+
namespace hal::input {
20+
void nunchuck_test()
21+
{
22+
using namespace boost::ut;
23+
using namespace std::literals;
24+
25+
"nunchuck::create()"_test = []() {
26+
// Setup
27+
// Exercise
28+
// Verify
29+
};
30+
};
31+
} // namespace hal::input

0 commit comments

Comments
 (0)