1- // SPDX-License-Identifier: MIT
2- // Copyright (c) 2018-2026 The Pybricks Authors
3-
41#include "py/mpconfig.h"
52
63#if PYBRICKS_PY_EXPERIMENTAL
74
8- #include "py/mphal.h"
95#include "py/obj.h"
106#include "py/runtime.h"
11- #include <math.h>
12-
13- #if defined(__ARM_ARCH_7M__ ) || defined(__ARM_ARCH_7EM__ )
14- #define IS_CORTEX_M 1
15- #define ACCEL_RAM __attribute__((section(".data"), noinline))
16- #else
17- #define IS_CORTEX_M 0
18- #define ACCEL_RAM
19- #endif
20-
21- // -----------------------------------------------------------------------------
22- // Core Math Engine
23- // -----------------------------------------------------------------------------
24- ACCEL_RAM static float fast_sin_internal (float theta ) {
25- float x = theta * 0.159154943f ;
26- x = theta - (float )((int )(x + (x > 0 ? 0.5f : -0.5f ))) * 6.2831853f ;
27- if (x > 1.5707963f ) x = 3.1415926f - x ;
28- else if (x < -1.5707963f ) x = -3.1415926f - x ;
29- float x2 = x * x ;
30- return x * (0.99999906f + x2 * (-0.16665554f + x2 * (0.00831190f + x2 * -0.00018488f )));
31- }
7+ #include "pybricks/experimental/odometry.h"
328
33- // -----------------------------------------------------------------------------
34- // High-Speed API Odometry (Pure Encoders, No Gyro)
35- // -----------------------------------------------------------------------------
36- static mp_obj_t experimental_odometry_benchmark (size_t n_args , const mp_obj_t * args ) {
9+ // This is the function that Python calls
10+ STATIC mp_obj_t experimental_odometry_benchmark (size_t n_args , const mp_obj_t * args ) {
3711 int num_iters = mp_obj_get_int (args [0 ]);
3812 float wheel_circ = mp_obj_get_float (args [1 ]);
39- float axle_track = mp_obj_get_float (args [2 ]); // Added so we can do wheel math
40-
41- // Receive the bound MicroPython methods for the motors
42- mp_obj_t right_angle_func = args [3 ];
43- mp_obj_t left_angle_func = args [4 ];
44-
45- float deg_to_mm = wheel_circ / 360.0f ;
46- float robot_x = 0.0f , robot_y = 0.0f ;
47- float current_heading_rad = 0.0f ;
48-
49- // motor.angle() safely returns an integer
50- int32_t last_r_deg = mp_obj_get_int (mp_call_function_0 (right_angle_func ));
51- int32_t last_l_deg = mp_obj_get_int (mp_call_function_0 (left_angle_func ));
52-
53- uint32_t start_time = mp_hal_ticks_ms ();
13+ float axle_track = mp_obj_get_float (args [2 ]);
14+ mp_obj_t right_func = args [3 ];
15+ mp_obj_t left_func = args [4 ];
5416
55- for (int i = 0 ; i < num_iters ; i ++ ) {
56- int32_t cur_r_deg = mp_obj_get_int (mp_call_function_0 (right_angle_func ));
57- int32_t cur_l_deg = mp_obj_get_int (mp_call_function_0 (left_angle_func ));
58-
59- // Calculate distance traveled by each wheel in this specific loop step
60- float delta_r_mm = (float )(cur_r_deg - last_r_deg ) * deg_to_mm ;
61- float delta_l_mm = (float )(cur_l_deg - last_l_deg ) * deg_to_mm ;
62-
63- // Kinematics: Center distance and heading change
64- float linear_delta = (delta_r_mm + delta_l_mm ) / 2.0f ;
65- float heading_delta_rad = (delta_r_mm - delta_l_mm ) / axle_track ;
66-
67- if (fabsf (linear_delta ) > 0.0001f || fabsf (heading_delta_rad ) > 0.0001f ) {
68- // Arc integration: use the average heading during the movement step
69- float avg_h_rad = current_heading_rad + (heading_delta_rad / 2.0f );
70-
71- robot_x += linear_delta * fast_sin_internal (avg_h_rad + 1.5707963f ); // cos
72- robot_y += linear_delta * fast_sin_internal (avg_h_rad ); // sin
73-
74- current_heading_rad += heading_delta_rad ;
75- }
76-
77- last_r_deg = cur_r_deg ;
78- last_l_deg = cur_l_deg ;
79-
80- // Yield for system stability
81- if ((i % 2000 ) == 0 ) {
82- mp_handle_pending (true);
83- mp_hal_delay_ms (1 );
84- }
85- }
86-
87- uint32_t dur = mp_hal_ticks_ms () - start_time ;
88- mp_obj_t tuple [5 ] = {
89- mp_obj_new_float_from_f ((float )dur / 1000.0f ),
90- mp_obj_new_int (num_iters ),
91- mp_obj_new_float_from_f ((float )num_iters / ((float )dur / 1000.0f )),
92- mp_obj_new_float_from_f (robot_x ),
93- mp_obj_new_float_from_f (robot_y )
94- };
95- return mp_obj_new_tuple (5 , tuple );
17+ return calculate_odometry (num_iters , wheel_circ , axle_track , right_func , left_func );
9618}
19+ // We define it as a MicroPython function object
20+ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN (experimental_odometry_benchmark_obj , 5 , 5 , experimental_odometry_benchmark );
9721
98- static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN (experimental_odometry_benchmark_obj , 5 , 5 , experimental_odometry_benchmark ) ;
99-
100- static const mp_rom_map_elem_t experimental_globals_table [] = {
101- { MP_ROM_QSTR (MP_QSTR___name__ ), MP_ROM_QSTR (MP_QSTR_experimental ) },
22+ // We map the C function to the Python name "odometry_benchmark"
23+ STATIC const mp_rom_map_elem_t experimental_globals_table [] = {
10224 { MP_ROM_QSTR (MP_QSTR_odometry_benchmark ), MP_ROM_PTR (& experimental_odometry_benchmark_obj ) },
10325};
104- static MP_DEFINE_CONST_DICT (pb_module_experimental_globals , experimental_globals_table ) ;
26+ STATIC MP_DEFINE_CONST_DICT (pb_module_experimental_globals , experimental_globals_table );
10527
28+ // This is the structure MicroPython looks for to register the module
10629const mp_obj_module_t pb_module_experimental = {
10730 .base = { & mp_type_module },
10831 .globals = (mp_obj_dict_t * )& pb_module_experimental_globals ,
10932};
11033
111- #if !MICROPY_MODULE_BUILTIN_SUBPACKAGES
112- MP_REGISTER_MODULE (MP_QSTR_pybricks_dot_experimental , pb_module_experimental );
113- #endif
34+ // CRITICAL: We do NOT need a MP_REGISTER_MODULE tag here because
35+ // the Pybricks build system handles registration via the Makefile/QSTR process.
11436
11537#endif // PYBRICKS_PY_EXPERIMENTAL
0 commit comments