|
10 | 10 | #include "py/runtime.h" |
11 | 11 | #include <math.h> |
12 | 12 |
|
| 13 | +// Pybricks Hardware I/O Headers |
13 | 14 | #include <pbio/tacho.h> |
14 | 15 | #include <pbio/drivebase.h> |
| 16 | +#include <pbio/port.h> |
15 | 17 |
|
16 | 18 | #if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) |
17 | 19 | #define IS_CORTEX_M 1 |
|
21 | 23 | #define ACCEL_RAM |
22 | 24 | #endif |
23 | 25 |
|
24 | | -// We define our own version of the objects based on the observed |
25 | | -// memory layout of the Spike Prime firmware. |
26 | | -typedef struct _pb_type_Motor_obj_t { |
27 | | - mp_obj_base_t base; |
28 | | - pbio_tacho_t *tacho; |
29 | | -} pb_type_Motor_obj_t; |
30 | | - |
31 | | -typedef struct _pb_type_DriveBase_obj_t { |
32 | | - mp_obj_base_t base; |
33 | | - pbio_drivebase_t *db; |
34 | | -} pb_type_DriveBase_obj_t; |
35 | | - |
36 | 26 | // ----------------------------------------------------------------------------- |
37 | | -// Core Math Engine |
| 27 | +// Core Math Engine (Optimized MiniMax Polynomial) |
38 | 28 | // ----------------------------------------------------------------------------- |
39 | 29 | ACCEL_RAM static float fast_sin_internal(float theta) { |
40 | | - float x = theta * 0.159154943f; |
41 | | - x = theta - (float)((int)(x + (x > 0 ? 0.5f : -0.5f))) * 6.2831853f; |
42 | | - if (x > 1.5707963f) x = 3.1415926f - x; |
43 | | - else if (x < -1.5707963f) x = -3.1415926f - x; |
| 30 | + float x = theta * 0.159154943f; // INV_TWO_PI |
| 31 | + x = theta - (float)((int)(x + (x > 0 ? 0.5f : -0.5f))) * 6.2831853f; // TWO_PI |
| 32 | + if (x > 1.5707963f) x = 3.1415926f - x; // PI - x |
| 33 | + else if (x < -1.5707963f) x = -3.1415926f - x; // -PI - x |
44 | 34 | float x2 = x * x; |
45 | 35 | return x * (0.99999906f + x2 * (-0.16665554f + x2 * (0.00831190f + x2 * -0.00018488f))); |
46 | 36 | } |
47 | 37 |
|
48 | 38 | // ----------------------------------------------------------------------------- |
49 | | -// High-Speed Odometry |
| 39 | +// High-Speed Hardware-Layer Odometry |
50 | 40 | // ----------------------------------------------------------------------------- |
51 | 41 | static mp_obj_t experimental_odometry_benchmark(size_t n_args, const mp_obj_t *args) { |
52 | 42 | int num_iters = mp_obj_get_int(args[0]); |
53 | 43 | float wheel_circ = mp_obj_get_float(args[1]); |
54 | 44 |
|
55 | | - // Direct casting from the Python object to our defined struct |
56 | | - pb_type_Motor_obj_t *m_right = (pb_type_Motor_obj_t *)MP_OBJ_TO_PTR(args[2]); |
57 | | - pb_type_Motor_obj_t *m_left = (pb_type_Motor_obj_t *)MP_OBJ_TO_PTR(args[3]); |
58 | | - pb_type_DriveBase_obj_t *db_obj = (pb_type_DriveBase_obj_t *)MP_OBJ_TO_PTR(args[4]); |
| 45 | + // 1. Unpack raw Port IDs from Python (e.g., ord('A') -> 65) |
| 46 | + pbio_port_id_t port_right = (pbio_port_id_t)mp_obj_get_int(args[2]); |
| 47 | + pbio_port_id_t port_left = (pbio_port_id_t)mp_obj_get_int(args[3]); |
| 48 | + |
| 49 | + // 2. Bind directly to the hardware tacho drivers |
| 50 | + pbio_tacho_t *tacho_r; |
| 51 | + pbio_tacho_t *tacho_l; |
| 52 | + |
| 53 | + // PBIO returns 0 (PBIO_SUCCESS) if the hardware exists on that port |
| 54 | + if (pbio_tacho_get_tacho(port_right, &tacho_r) != 0 || |
| 55 | + pbio_tacho_get_tacho(port_left, &tacho_l) != 0) { |
| 56 | + mp_printf(&mp_plat_print, "C-ERROR: Failed to bind to hardware tachos on specified ports.\n"); |
| 57 | + mp_obj_t err[5] = {mp_obj_new_float_from_f(0), mp_obj_new_int(0), mp_obj_new_float_from_f(0), mp_obj_new_float_from_f(0), mp_obj_new_float_from_f(0)}; |
| 58 | + return mp_obj_new_tuple(5, err); |
| 59 | + } |
| 60 | + |
| 61 | + // 3. Unpack the DriveBase heading function |
| 62 | + mp_obj_t db_heading_func = args[4]; |
59 | 63 |
|
60 | 64 | float deg_to_mm = wheel_circ / 360.0f; |
61 | 65 | float robot_x = 0.0f, robot_y = 0.0f; |
62 | 66 |
|
63 | 67 | pbio_angle_t ang_l, ang_r; |
64 | | - int32_t h_mdeg; |
65 | | - |
66 | | - // Initial capture |
67 | | - pbio_tacho_get_angle(m_left->tacho, &ang_l); |
68 | | - pbio_tacho_get_angle(m_right->tacho, &ang_r); |
69 | | - pbio_drivebase_get_state_user(db_obj->db, NULL, NULL, &h_mdeg, NULL); |
| 68 | + |
| 69 | + // Capture initial hardware state |
| 70 | + pbio_tacho_get_angle(tacho_l, &ang_l); |
| 71 | + pbio_tacho_get_angle(tacho_r, &ang_r); |
| 72 | + int32_t last_heading_deg = mp_obj_get_int(mp_call_function_0(db_heading_func)); |
70 | 73 |
|
71 | 74 | float last_l_mm = ((float)ang_l.rotations * 360.0f + (float)ang_l.millidegrees / 1000.0f) * deg_to_mm; |
72 | 75 | float last_r_mm = ((float)ang_r.rotations * 360.0f + (float)ang_r.millidegrees / 1000.0f) * deg_to_mm; |
73 | 76 | float last_lin = (last_l_mm + last_r_mm) / 2.0f; |
74 | | - float last_heading = (float)h_mdeg / 1000.0f; |
75 | 77 |
|
76 | 78 | uint32_t start_time = mp_hal_ticks_ms(); |
77 | 79 |
|
78 | 80 | for (int i = 0; i < num_iters; i++) { |
79 | | - // We use the pointers we grabbed directly from the structs |
80 | | - pbio_tacho_get_angle(m_left->tacho, &ang_l); |
81 | | - pbio_tacho_get_angle(m_right->tacho, &ang_r); |
82 | | - pbio_drivebase_get_state_user(db_obj->db, NULL, NULL, &h_mdeg, NULL); |
| 81 | + // Direct C-speed hardware reads |
| 82 | + pbio_tacho_get_angle(tacho_l, &ang_l); |
| 83 | + pbio_tacho_get_angle(tacho_r, &ang_r); |
| 84 | + |
| 85 | + // Single VM call for complex gyro fusion heading |
| 86 | + int32_t cur_heading_deg = mp_obj_get_int(mp_call_function_0(db_heading_func)); |
83 | 87 |
|
84 | 88 | float cur_l_mm = ((float)ang_l.rotations * 360.0f + (float)ang_l.millidegrees / 1000.0f) * deg_to_mm; |
85 | 89 | float cur_r_mm = ((float)ang_r.rotations * 360.0f + (float)ang_r.millidegrees / 1000.0f) * deg_to_mm; |
86 | 90 | float cur_lin = (cur_l_mm + cur_r_mm) / 2.0f; |
87 | | - float cur_heading = (float)h_mdeg / 1000.0f; |
88 | 91 |
|
89 | 92 | float linear_delta = cur_lin - last_lin; |
90 | | - float heading_delta = cur_heading - last_heading; |
| 93 | + float heading_delta = (float)(cur_heading_deg - last_heading_deg); |
91 | 94 |
|
92 | 95 | if (fabsf(linear_delta) > 0.0001f) { |
93 | | - float avg_h_rad = (last_heading + (heading_delta / 2.0f)) * 0.01745329f; |
94 | | - robot_x += linear_delta * fast_sin_internal(avg_h_rad + 1.5707963f); |
95 | | - robot_y += linear_delta * fast_sin_internal(avg_h_rad); |
| 96 | + float avg_h_rad = ((float)last_heading_deg + (heading_delta / 2.0f)) * 0.01745329f; |
| 97 | + robot_x += linear_delta * fast_sin_internal(avg_h_rad + 1.5707963f); // cos |
| 98 | + robot_y += linear_delta * fast_sin_internal(avg_h_rad); // sin |
96 | 99 | } |
97 | 100 |
|
98 | 101 | last_lin = cur_lin; |
99 | | - last_heading = cur_heading; |
| 102 | + last_heading_deg = cur_heading_deg; |
100 | 103 |
|
| 104 | + // Yield to maintain BLE connection and reset watchdog |
101 | 105 | if ((i % 2000) == 0) { |
102 | 106 | mp_handle_pending(true); |
103 | 107 | mp_hal_delay_ms(1); |
104 | 108 | } |
105 | 109 | } |
106 | 110 |
|
107 | 111 | uint32_t dur = mp_hal_ticks_ms() - start_time; |
| 112 | + float duration_s = (float)dur / 1000.0f; |
| 113 | + |
108 | 114 | mp_obj_t tuple[5] = { |
109 | | - mp_obj_new_float_from_f((float)dur / 1000.0f), |
| 115 | + mp_obj_new_float_from_f(duration_s), |
110 | 116 | mp_obj_new_int(num_iters), |
111 | | - mp_obj_new_float_from_f((float)num_iters / ((float)dur / 1000.0f)), |
| 117 | + mp_obj_new_float_from_f((float)num_iters / duration_s), |
112 | 118 | mp_obj_new_float_from_f(robot_x), |
113 | 119 | mp_obj_new_float_from_f(robot_y) |
114 | 120 | }; |
|
0 commit comments