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{®, 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{®, 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