GCC Code Coverage Report


Directory: libhal-mpl/
File: libhal-mpl/src/mpl3115a2.cpp
Date: 2024-03-31 12:24:12
Exec Total Coverage
Lines: 0 117 0.0%
Functions: 0 14 0.0%
Branches: 0 108 0.0%

Line Branch Exec Source
1 #include <libhal-mpl/mpl3115a2.hpp>
2
3 #include <array>
4
5 #include <libhal-util/i2c.hpp>
6
7 #include "mpl3115a2_reg.hpp"
8
9 using namespace std::literals;
10 namespace hal::mpl {
11 namespace {
12 /**
13 * @brief Set the ctrl_reg1_alt bit in ctrl_reg1 to the value corresponding to
14 * 'mode'
15 * @param p_i2c The I2C peripheral used for communication with the device.
16 * @param p_mode: The desired operation mode
17 */
18 hal::status set_mode(hal::i2c* p_i2c, mpl3115a2::mode p_mode)
19 {
20 // Read value of ctrl_reg1
21 auto ctrl_buffer =
22 HAL_CHECK(hal::write_then_read<1>(*p_i2c,
23 device_address,
24 std::array<hal::byte, 1>{ ctrl_reg1 },
25 hal::never_timeout()));
26
27 hal::byte reg_val = ctrl_buffer[0];
28 if ((reg_val & ctrl_reg1_alt) != static_cast<int>(p_mode)) {
29 // Set mode ctrl reg bit to binary value of 'mode'
30 if (p_mode == mpl3115a2::mode::barometer) {
31 reg_val &= ~ctrl_reg1_alt; // Set the bit to 0
32 } else {
33 reg_val |= ctrl_reg1_alt; // Set the bit to 1
34 }
35 }
36
37 HAL_CHECK(hal::write(*p_i2c,
38 device_address,
39 std::array<hal::byte, 2>{ ctrl_reg1, reg_val },
40 hal::never_timeout()));
41
42 return hal::success();
43 }
44
45 struct modify_reg_param_t
46 {
47 hal::byte address;
48 hal::byte bits_to_set;
49 };
50
51 /**
52 * @brief Set bits in a register without overwriting existing register state
53 * @param p_i2c The I2C peripheral used for communication with the device.
54 * @param p_reg_addr: 8 bit register address
55 * @param p_bits_to_set: 8 bit value specifying which bits to set in register
56 */
57 hal::status modify_reg_bits(hal::i2c* p_i2c, modify_reg_param_t p_reg)
58 {
59 // Read old register value
60 auto reg_buffer =
61 HAL_CHECK(hal::write_then_read<1>(*p_i2c,
62 device_address,
63 std::array<hal::byte, 1>{ p_reg.address },
64 hal::never_timeout()));
65
66 // Set specified bits while maintaining old values
67 hal::byte updated_reg = reg_buffer[0] | p_reg.bits_to_set;
68 HAL_CHECK(hal::write(*p_i2c,
69 device_address,
70 std::array<hal::byte, 2>{ p_reg.address, updated_reg },
71 hal::never_timeout()));
72
73 return hal::success();
74 }
75
76 /**
77 * @brief Wait for the reset bit in ctrl_reg1 to be set.
78 Catches and ignores expected std::errc::no_such_device_or_address.
79 * @param p_i2c The I2C peripheral used for communication with the device.
80 */
81 hal::status poll_reset(hal::i2c* p_i2c)
82 {
83 bool flag_set = true;
84 uint16_t retries = 0;
85
86 // Lambda function to poll ctrl_reg1 reset flag
87 auto poll_function = [&p_i2c, &flag_set]() -> hal::status {
88 std::array<hal::byte, 1> status_buffer{};
89 HAL_CHECK(hal::write_then_read(*p_i2c,
90 device_address,
91 std::array<hal::byte, 1>{ ctrl_reg1 },
92 status_buffer,
93 hal::never_timeout()));
94 flag_set = ((status_buffer[0] & ctrl_reg1_rst) != 0);
95 return hal::success();
96 };
97
98 // std::errc error code handler. std::errc::no_such_device_or_address is
99 // expected during reset as the device comes online.
100 auto err_handler = [](std::errc e_code) -> hal::status {
101 if (e_code != std::errc::no_such_device_or_address) {
102 return hal::new_error(e_code);
103 }
104 return hal::success();
105 };
106
107 // Perform polling
108 while (flag_set && (retries < mpl3115a2::default_max_polling_retries)) {
109 HAL_CHECK(hal::attempt(poll_function, err_handler));
110 retries++;
111 }
112
113 return hal::success();
114 }
115
116 struct poll_flag_param_t
117 {
118
119 /// 8 bit value specifying the register address
120 hal::byte address;
121 /// 8 bit value specifying which bit(s) to check
122 hal::byte flag;
123 /// The state of the bit to finish polling
124 bool desired_state;
125 };
126
127 /**
128 * @brief Wait for a specified flag bit in a register to be set to the desired
129 * state.
130 * @param p_i2c The I2C peripheral used for communication with the device.
131 * reached, the function will exit.
132 */
133 hal::status poll_flag(hal::i2c* p_i2c, poll_flag_param_t p_poll)
134 {
135 std::array<hal::byte, 1> status_payload{ p_poll.address };
136 std::array<hal::byte, 1> status_buffer{};
137 uint16_t retries = 0;
138 bool flag_set = true;
139
140 while (flag_set && (retries < mpl3115a2::default_max_polling_retries)) {
141 HAL_CHECK(hal::write_then_read(*p_i2c,
142 device_address,
143 status_payload,
144 status_buffer,
145 hal::never_timeout()));
146
147 if (p_poll.desired_state) {
148 flag_set = ((status_buffer[0] & p_poll.flag) == 0);
149 } else {
150 flag_set = ((status_buffer[0] & p_poll.flag) != 0);
151 }
152 retries++;
153 }
154
155 return hal::success();
156 }
157
158 /**
159 * @brief Trigger one-shot measurement by setting ctrl_reg1_ost bit in
160 * ctrl_reg1.
161 * @param p_i2c The I2C peripheral used for communication with the device.
162 */
163 hal::status initiate_one_shot(hal::i2c* p_i2c)
164 {
165 // Wait for one-shot flag to clear
166 poll_flag(
167 p_i2c,
168 { .address = ctrl_reg1, .flag = ctrl_reg1_ost, .desired_state = false });
169
170 // Set ost bit in ctrl_reg1 - initiate one shot measurement
171 HAL_CHECK(modify_reg_bits(
172 p_i2c, { .address = ctrl_reg1, .bits_to_set = ctrl_reg1_ost }));
173
174 return hal::success();
175 }
176 } // namespace
177
178 mpl3115a2::mpl3115a2(hal::i2c& p_i2c)
179 : m_i2c(&p_i2c)
180 , m_sensor_mode(mode::altimeter)
181 {
182 }
183
184 result<mpl3115a2> mpl3115a2::create(hal::i2c& p_i2c)
185 {
186 mpl3115a2 mpl_dev(p_i2c);
187
188 // sanity check
189 auto whoami_buffer =
190 HAL_CHECK(hal::write_then_read<1>(p_i2c,
191 device_address,
192 std::array<hal::byte, 1>{ whoami_r },
193 hal::never_timeout()));
194
195 if (whoami_buffer[0] != 0xC4) {
196 return hal::new_error(std::errc::no_such_device);
197 }
198
199 // software reset
200 modify_reg_bits(&p_i2c,
201 { .address = ctrl_reg1, .bits_to_set = ctrl_reg1_rst });
202
203 poll_reset(&p_i2c);
204
205 // set oversampling ratio to 2^128 and set altitude mode
206 modify_reg_bits(
207 &p_i2c,
208 { .address = ctrl_reg1, .bits_to_set = ctrl_reg1_os128 | ctrl_reg1_alt });
209
210 // enable data ready events for pressure/altitude and temperature
211 std::array<hal::byte, 2> dr_payload{
212 pt_data_cfg_r, pt_data_cfg_tdefe | pt_data_cfg_pdefe | pt_data_cfg_drem
213 };
214 HAL_CHECK(
215 hal::write(p_i2c, device_address, dr_payload, hal::never_timeout()));
216
217 return mpl_dev;
218 }
219
220 hal::status mpl3115a2::set_sea_pressure(float p_sea_level_pressure)
221 {
222 // divide by 2 to convert to 2Pa per LSB
223 auto two_pa = static_cast<std::uint16_t>(p_sea_level_pressure / 2.0f);
224 auto two_pa_hi = static_cast<hal::byte>((two_pa & 0xFF00) >> 8);
225 auto two_pa_lo = static_cast<hal::byte>(two_pa & 0x00FF);
226
227 // write result to register
228 std::array<hal::byte, 3> slp_payload = {
229 bar_in_msb_r,
230 two_pa_hi, // msb
231 two_pa_lo // lsb
232 };
233
234 HAL_CHECK(
235 hal::write(*m_i2c, device_address, slp_payload, hal::never_timeout()));
236
237 return hal::success();
238 }
239
240 hal::status mpl3115a2::set_altitude_offset(int8_t p_offset)
241 {
242 std::array<hal::byte, 2> offset_payload = { off_h_r, hal::byte(p_offset) };
243 HAL_CHECK(
244 hal::write(*m_i2c, device_address, offset_payload, hal::never_timeout()));
245
246 return hal::success();
247 }
248
249 hal::result<mpl3115a2::temperature_read_t> mpl3115a2::read_temperature()
250 {
251 constexpr float temp_conversion_factor = 256.0f;
252
253 initiate_one_shot(m_i2c);
254
255 poll_flag(m_i2c,
256 { .address = status_r, .flag = status_tdr, .desired_state = true });
257
258 // Read data from out_t_msb_r and out_t_lsb_r
259 auto temp_buffer =
260 HAL_CHECK(hal::write_then_read<2>(*m_i2c,
261 device_address,
262 std::array<hal::byte, 1>{ out_t_msb_r },
263 hal::never_timeout()));
264
265 auto temp_reading = (temp_buffer[0] << 8) | temp_buffer[1];
266 return mpl3115a2::temperature_read_t{
267 static_cast<float>(temp_reading) / temp_conversion_factor,
268 };
269 }
270
271 hal::result<mpl3115a2::pressure_read_t> mpl3115a2::read_pressure()
272 {
273 // Note: 64 -> Pa, 6400 -> kPa
274 constexpr float pressure_conversion_factor = 64.0f;
275
276 if (m_sensor_mode != mode::barometer) {
277 set_mode(m_i2c, mode::barometer);
278 m_sensor_mode = mode::barometer;
279 }
280
281 initiate_one_shot(m_i2c);
282
283 poll_flag(m_i2c,
284 { .address = status_r, .flag = status_pdr, .desired_state = true });
285
286 // Read data from out_p_msb_r, out_p_csb_r, and out_p_lsb_r
287 auto pres_buffer =
288 HAL_CHECK(hal::write_then_read<3>(*m_i2c,
289 device_address,
290 std::array<hal::byte, 1>{ out_p_msb_r },
291 hal::never_timeout()));
292
293 uint32_t pressure_reading = uint32_t(pres_buffer[0]) << 16 |
294 uint32_t(pres_buffer[1]) << 8 |
295 uint32_t(pres_buffer[2]);
296
297 return mpl3115a2::pressure_read_t{
298 static_cast<float>(pressure_reading) / pressure_conversion_factor,
299 };
300 }
301
302 hal::result<mpl3115a2::altitude_read_t> mpl3115a2::read_altitude()
303 {
304 constexpr float altitude_conversion_factor = 65536.0f;
305
306 if (m_sensor_mode != mode::altimeter) {
307 set_mode(m_i2c, mode::altimeter);
308 m_sensor_mode = mode::altimeter;
309 }
310
311 initiate_one_shot(m_i2c);
312
313 poll_flag(m_i2c,
314 { .address = status_r, .flag = status_pdr, .desired_state = true });
315
316 // Read data from out_p_msb_r, out_p_csb_r, and out_p_lsb_r
317 auto alt_buffer =
318 HAL_CHECK(hal::write_then_read<3>(*m_i2c,
319 device_address,
320 std::array<hal::byte, 1>{ out_p_msb_r },
321 hal::never_timeout()));
322
323 int32_t alt_reading = int32_t(alt_buffer[0]) << 24 |
324 int32_t(alt_buffer[1]) << 16 |
325 int32_t(alt_buffer[2]) << 8;
326
327 return mpl3115a2::altitude_read_t{
328 static_cast<float>(alt_reading) / altitude_conversion_factor,
329 };
330 }
331
332 } // namespace hal::mpl
333