Skip to content

Commit cf8f05c

Browse files
committed
added a spi driver for the adxl345 accelerometer
1 parent 56f5b18 commit cf8f05c

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
#ifndef KLIB_ADXL345_HPP
2+
#define KLIB_ADXL345_HPP
3+
4+
#include <cstdint>
5+
6+
#include <klib/vector3.hpp>
7+
#include <klib/multispan.hpp>
8+
9+
namespace klib::hardware::accelerometer::detail {
10+
class adxl345_base {
11+
public:
12+
/**
13+
* @brief Wakeup rate options for POWER_CTL register (0x2D)
14+
*
15+
*/
16+
enum class wakeup : uint8_t {
17+
hz_8 = 0x00,
18+
hz_4 = 0x01,
19+
hz_2 = 0x02,
20+
hz_1 = 0x03
21+
};
22+
23+
/**
24+
* @brief Interrupt output mapping for INT_MAP register (0x2F)
25+
*
26+
*/
27+
enum class output_map {
28+
int1 = 0x00,
29+
int2 = 0x01
30+
};
31+
32+
// Data rate options for BW_RATE register (0x2C)
33+
enum class data_rate : uint8_t {
34+
hz_3200 = 0x0F,
35+
hz_1600 = 0x0E,
36+
hz_800 = 0x0D,
37+
hz_400 = 0x0C,
38+
hz_200 = 0x0B,
39+
hz_100 = 0x0A,
40+
hz_50 = 0x09,
41+
hz_25 = 0x08,
42+
hz_12_5 = 0x07,
43+
hz_6_25 = 0x06,
44+
hz_3_13 = 0x05,
45+
hz_1_56 = 0x04,
46+
hz_0_78 = 0x03,
47+
hz_0_39 = 0x02,
48+
hz_0_20 = 0x01,
49+
hz_0_10 = 0x00
50+
};
51+
52+
// Data format options for DATA_FORMAT register (0x31)
53+
enum class range : uint8_t {
54+
g2 = 0x00,
55+
g4 = 0x01,
56+
g8 = 0x02,
57+
g16 = 0x03
58+
};
59+
60+
// FIFO control options for FIFO_CTL register (0x38)
61+
enum class fifo_ctl : uint8_t {
62+
mode_bypass = 0x00,
63+
mode_fifo = 0x40,
64+
mode_stream = 0x80,
65+
mode_trigger = 0xC0,
66+
samples_mask = 0x1F
67+
};
68+
69+
protected:
70+
// Register addresses for ADXL345
71+
enum class reg : uint8_t {
72+
DEVID = 0x00,
73+
THRESH_TAP = 0x1D,
74+
OFSX = 0x1E,
75+
OFSY = 0x1F,
76+
OFSZ = 0x20,
77+
DUR = 0x21,
78+
LATENT = 0x22,
79+
WINDOW = 0x23,
80+
THRESH_ACT = 0x24,
81+
THRESH_INACT = 0x25,
82+
TIME_INACT = 0x26,
83+
ACT_INACT_CTL = 0x27,
84+
THRESH_FF = 0x28,
85+
TIME_FF = 0x29,
86+
TAP_AXES = 0x2A,
87+
ACT_TAP_STATUS = 0x2B,
88+
BW_RATE = 0x2C,
89+
POWER_CTL = 0x2D,
90+
INT_ENABLE = 0x2E,
91+
INT_MAP = 0x2F,
92+
INT_SOURCE = 0x30,
93+
DATA_FORMAT = 0x31,
94+
DATAX0 = 0x32,
95+
DATAX1 = 0x33,
96+
DATAY0 = 0x34,
97+
DATAY1 = 0x35,
98+
DATAZ0 = 0x36,
99+
DATAZ1 = 0x37,
100+
FIFO_CTL = 0x38,
101+
FIFO_STATUS = 0x39
102+
};
103+
};
104+
}
105+
106+
namespace klib::hardware::accelerometer {
107+
/**
108+
* @brief SPI driver for the adxl345 accelerometer
109+
*
110+
* @details The adxl345 has a maximum spi clock speed of 5 MHz
111+
*
112+
* @tparam Bus
113+
*/
114+
template <typename Bus>
115+
class adxl345_spi : public detail::adxl345_base {
116+
protected:
117+
// get a alias for easier access to registers
118+
using reg = detail::adxl345_base::reg;
119+
120+
public:
121+
/**
122+
* @brief Init the ADXL345 sensor
123+
*
124+
*/
125+
void init(const detail::adxl345_base::data_rate rate, const detail::adxl345_base::range range = detail::adxl345_base::range::g2) {
126+
// set the range. This also makes sure the data format register is
127+
// set correctly for 4-wire spi mode.
128+
set_range(range);
129+
130+
// set the data rate
131+
set_data_rate(rate);
132+
133+
// disable all the interrupts
134+
set_interrupts(false, false, false, false, false, false, false, false);
135+
136+
// disable the fifo
137+
write_reg(reg::FIFO_CTL, static_cast<uint8_t>(fifo_ctl::mode_bypass));
138+
139+
// set the device to measurement mode
140+
power_control(true, false, true, false, detail::adxl345_base::wakeup::hz_8);
141+
}
142+
143+
/**
144+
* @brief Set the data rate of the sensor
145+
*
146+
* @param rate
147+
*/
148+
void set_data_rate(const detail::adxl345_base::data_rate rate) {
149+
write_reg(reg::BW_RATE, static_cast<uint8_t>(rate));
150+
}
151+
152+
/**
153+
* @brief Write to the POWER_CTL register to control power settings
154+
*
155+
* @param link
156+
* @param auto_sleep
157+
* @param measure
158+
* @param sleep
159+
* @param wakeup
160+
*/
161+
void power_control(
162+
const bool link = false, const bool auto_sleep = false, const bool measure = true,
163+
const bool sleep = false, const detail::adxl345_base::wakeup wakeup = false)
164+
{
165+
const uint8_t ctl = (
166+
(link << 5) | (auto_sleep << 4) | (measure << 3) | (sleep << 2) | static_cast<uint8_t>(wakeup)
167+
);
168+
169+
write_reg(reg::POWER_CTL, ctl);
170+
}
171+
172+
/**
173+
* @brief Enable/Disable interrupt sources
174+
*
175+
* @param data_ready
176+
* @param single_tap
177+
* @param double_tap
178+
* @param activity
179+
* @param inactivity
180+
* @param freefall
181+
* @param watermark
182+
* @param overrun
183+
*/
184+
void set_interrupts(const bool data_ready, const bool single_tap = false, const bool double_tap = false,
185+
const bool activity = false, const bool inactivity = false, const bool freefall = false,
186+
const bool watermark = false, const bool overrun = false)
187+
{
188+
const uint8_t int_enable = (
189+
(overrun << 7) | (watermark << 6) | (freefall << 5) | (inactivity << 4) |
190+
(activity << 3) | (double_tap << 2) | (single_tap << 1) | data_ready
191+
);
192+
193+
write_reg(reg::INT_ENABLE, int_enable);
194+
}
195+
196+
/**
197+
* @brief Set the interrupt mapping for each interrupt source
198+
*
199+
* @param data_ready
200+
* @param single_tap
201+
* @param double_tap
202+
* @param activity
203+
* @param inactivity
204+
* @param freefall
205+
* @param watermark
206+
* @param overrun
207+
*/
208+
void set_interrupt_map(
209+
const detail::adxl345_base::output_map data_ready, const detail::adxl345_base::output_map single_tap = false,
210+
const detail::adxl345_base::output_map double_tap = false, const detail::adxl345_base::output_map activity = false,
211+
const detail::adxl345_base::output_map inactivity = false, const detail::adxl345_base::output_map freefall = false,
212+
const detail::adxl345_base::output_map watermark = false, const detail::adxl345_base::output_map overrun = false)
213+
{
214+
const uint8_t int_map = (
215+
(static_cast<uint8_t>(overrun) << 7) | (static_cast<uint8_t>(watermark) << 6) |
216+
(static_cast<uint8_t>(freefall) << 5) | (static_cast<uint8_t>(inactivity) << 4) |
217+
(static_cast<uint8_t>(activity) << 3) | (static_cast<uint8_t>(double_tap) << 2) |
218+
(static_cast<uint8_t>(single_tap) << 1) | static_cast<uint8_t>(data_ready)
219+
);
220+
221+
write_reg(reg::INT_MAP, int_map);
222+
}
223+
224+
/**
225+
* @brief Set the range of the accelerometer
226+
*
227+
* @param range
228+
*/
229+
void set_range(const detail::adxl345_base::range range) {
230+
// we need to be sure the data_format register is set correctly. We only
231+
// support 4-wire spi mode. We also turn on full resolution mode.
232+
const uint8_t data_format = (
233+
static_cast<uint8_t>(range) | (0x1 << 3)
234+
);
235+
236+
write_reg(reg::DATA_FORMAT, data_format);
237+
}
238+
239+
/**
240+
* @brief Get the range setting of the accelerometer
241+
*
242+
* @return detail::adxl345_base::range
243+
*/
244+
detail::adxl345_base::range get_range() {
245+
// read the data format register
246+
const uint8_t reg = static_cast<uint8_t>(reg::DATA_FORMAT) | (0b10 << 6);
247+
uint8_t data_format = 0;
248+
249+
// read from the bus
250+
Bus::write_read(
251+
std::span{&reg, sizeof(reg)}, std::span<uint8_t>{&data_format, sizeof(data_format)}
252+
);
253+
254+
// extract the range bits and return
255+
return static_cast<detail::adxl345_base::range>(data_format & 0x03);
256+
}
257+
258+
/**
259+
* @brief Read all the axis data and convert to mg
260+
*
261+
* @return klib::vector3i
262+
*/
263+
klib::vector3i read_axis() const {
264+
// return the axis data in mg (we multipyly by 39 and divide by 10 to
265+
// get the 3.9 mg/LSB from the datasheet)
266+
return ((raw_axis() * 39) / 10);
267+
}
268+
269+
/**
270+
* @brief Read all the axis and convert to Gs
271+
*
272+
* @return klib::vector3<float>
273+
*/
274+
klib::vector3<float> read_axis_g() const {
275+
// return the axis data in g (we divide by 256 to
276+
// get the 1 g/LSB from the datasheet)
277+
return (raw_axis().template cast<float>() / 256.f);
278+
}
279+
280+
/**
281+
* @brief Read all the axis data in raw format
282+
*
283+
* @return klib::vector3i
284+
*/
285+
klib::vector3i raw_axis() const {
286+
// Multi-byte read: set MB (bit 6) and read (bit 7) in address
287+
const uint8_t reg = static_cast<uint8_t>(reg::DATAX0) | (0b11 << 6);
288+
289+
// buffer to hold the read data. Note the first byte is dummy for the write
290+
uint8_t buffer[7] = {};
291+
292+
// Write register address, then read 6 bytes
293+
Bus::write_read(std::span{&reg, sizeof(reg)}, buffer);
294+
295+
int16_t x = static_cast<int16_t>((buffer[2] << 8) | buffer[1]);
296+
int16_t y = static_cast<int16_t>((buffer[4] << 8) | buffer[3]);
297+
int16_t z = static_cast<int16_t>((buffer[6] << 8) | buffer[5]);
298+
299+
return klib::vector3i(x, y, z);
300+
}
301+
302+
protected:
303+
/**
304+
* @brief Write to a register over SPI
305+
*
306+
* @param reg_addr
307+
* @param value
308+
*/
309+
void write_reg(reg reg_addr, uint8_t value) const {
310+
// combine the register address and value to a buffer. Note
311+
// the first bit of the register address is 0 for write
312+
const uint8_t buffer[2] = {static_cast<uint8_t>(reg_addr), value};
313+
314+
// write the buffer to the bus
315+
Bus::write(buffer);
316+
}
317+
};
318+
};
319+
320+
#endif

0 commit comments

Comments
 (0)