Difference between revisions of "S24: Team TerraByte"
m (→Hardware Design) |
(→TerraByte) |
||
(195 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | == | + | == TerraByte == |
− | + | ||
+ | [[File:Tb.jpeg|400px]] [[File:Demo.gif|500px]] | ||
+ | |||
<BR/> | <BR/> | ||
== Abstract == | == Abstract == | ||
− | + | The objective of this project was to construct an autonomous vehicle capable of navigating to a specified destination while avoiding obstacles along its path. The destination is selected through an Android application, which communicates the destination coordinates to the vehicle via Bluetooth. The Sensor and Bridge module receives these coordinates and transmits them to the Geo module via CAN. Subsequently, the Geo module utilizes GPS and Compass data to guide the vehicle to the designated destination. The Driver module receives inputs from the ultrasonic sensors of the Sensor and Bridge module and directives from the Geo module, then sends appropriate commands to the motor module. Finally, the Motor module propels the vehicle according to the instructions provided by the Driver module. The primary focus of this project lies in the CAN communication among the four modules: Sensor and Bridge module, Geo module, Driver module, and Motor module. | |
=== Introduction === | === Introduction === | ||
Line 17: | Line 19: | ||
=== Team Members & Responsibilities === | === Team Members & Responsibilities === | ||
− | + | ||
+ | [[File:Terrabyte_group.png]] | ||
Gitlab Project Link - [https://gitlab.com/minalupadhye/C243_terrabyte C243_TerraByte] | Gitlab Project Link - [https://gitlab.com/minalupadhye/C243_terrabyte C243_TerraByte] | ||
Line 230: | Line 233: | ||
| GeekPi [https://www.amazon.com/GeeekPi-Interface-Adapter-Backlight-Raspberry/dp/B07QLRD3TM/ref=sr_1_6?crid=1I6PVXGLX2R0N&dib=eyJ2IjoiMSJ9.Km0pYGd3Z_UiOxae2a5kPrX9aBlfEzUuw8QiWd4BVduXwwUiQKbCWLBnH5OyDxNXKFOlMjSvg4H4gbT8EzXGE_Cjlq91mT85H6FuKzEDhj1dFqt_rLk9VhKVzkL0-rULG0D4HOteOLa2EfsECRi2kp44swfzGgQ2eMd-KFMJF-J-73NdhkAroZhH-8f9JVXzsMYgP4l1i3kwCSH1HhFEhs6Het28WMHQ9BbcfF2i8TU.18KYQ6mD7zd8rDc8bmVyu9xvQ0ky4afXwJ2mUsT4DOo&dib_tag=se&keywords=lcd+module+20x4&qid=1710280857&sprefix=lcd+module+20x4%2Caps%2C136&sr=8-6] | | GeekPi [https://www.amazon.com/GeeekPi-Interface-Adapter-Backlight-Raspberry/dp/B07QLRD3TM/ref=sr_1_6?crid=1I6PVXGLX2R0N&dib=eyJ2IjoiMSJ9.Km0pYGd3Z_UiOxae2a5kPrX9aBlfEzUuw8QiWd4BVduXwwUiQKbCWLBnH5OyDxNXKFOlMjSvg4H4gbT8EzXGE_Cjlq91mT85H6FuKzEDhj1dFqt_rLk9VhKVzkL0-rULG0D4HOteOLa2EfsECRi2kp44swfzGgQ2eMd-KFMJF-J-73NdhkAroZhH-8f9JVXzsMYgP4l1i3kwCSH1HhFEhs6Het28WMHQ9BbcfF2i8TU.18KYQ6mD7zd8rDc8bmVyu9xvQ0ky4afXwJ2mUsT4DOo&dib_tag=se&keywords=lcd+module+20x4&qid=1710280857&sprefix=lcd+module+20x4%2Caps%2C136&sr=8-6] | ||
| 1 | | 1 | ||
− | | $ | + | | $10.99 |
|- | |- | ||
! scope="row"| 5 | ! scope="row"| 5 | ||
Line 236: | Line 239: | ||
| Adafruit [https://www.adafruit.com/product/2479] | | Adafruit [https://www.adafruit.com/product/2479] | ||
| 1 | | 1 | ||
− | | $ | + | | $17.50 |
|- | |- | ||
! scope="row"| 6 | ! scope="row"| 6 | ||
Line 242: | Line 245: | ||
| Adafruit [https://www.adafruit.com/product/4019] | | Adafruit [https://www.adafruit.com/product/4019] | ||
| 4 | | 4 | ||
− | | $ | + | | $6.95 each |
|- | |- | ||
! scope="row"| 7 | ! scope="row"| 7 | ||
Line 248: | Line 251: | ||
| Adafruit [https://www.adafruit.com/product/746] | | Adafruit [https://www.adafruit.com/product/746] | ||
| 1 | | 1 | ||
− | | $ | + | | $29.95 |
|- | |- | ||
! scope="row"| 8 | ! scope="row"| 8 | ||
Line 254: | Line 257: | ||
| Adafruit [https://www.adafruit.com/product/4413] | | Adafruit [https://www.adafruit.com/product/4413] | ||
| 1 | | 1 | ||
− | | $ | + | | $29.90 |
|- | |- | ||
! scope="row"| 9 | ! scope="row"| 9 | ||
| GPS Antenna | | GPS Antenna | ||
− | | ? | + | | Antenna[https://www.amazon.com/Bingfu-Waterproof-Navigation-Connector-Tracking/dp/B07R7RC96G/ref=sr_1_1_sspa?crid=1M2FRFNQWRSSF&dib=eyJ2IjoiMSJ9.LNXUoYBLYPyk7t1JLvEu-o_GPhNOuBu_7rVYh0eJg9XBQCJvGpfFSVtVotXbv1bw_BJEEPuUwDNAl24dDeWCnh9MHA8nxT5PDxr-oczNt6H7DKjpjC-1vaY_Z1U6b_xz1jDgcDsnomMVUPTbVEZe1BVZG6OmsKC3SMv8WsSdmPI7hKo30InhNkYpnO1cguRNvkxdFcUJsbqttli5nG6lBHqLYh9-cd42bzcKObGW6K2YsmCjM1sFjMvVSXoGH9M24pzTTFSw2BBQtyC9MjsCa1q9Fo6ltoXSY4pBzUuRO18.avmXqc1aaG0GlZC6W6u2Z1Y1aY4925nkvX4yV5xG10g&dib_tag=se&keywords=gps%2Bantenna&qid=1716602993&s=electronics&sprefix=gps%2Bantenn%2Celectronics%2C139&sr=1-1-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&th=1] |
| 1 | | 1 | ||
− | | $ | + | | $8.90 |
|- | |- | ||
! scope="row"| 10 | ! scope="row"| 10 | ||
− | | RPM | + | | RPM Sensor |
− | | - | + | | Reed switch [https://www.amazon.com/WOWOONE-Normally-Induction-2-5mm%C3%9714mm-Multi-Use/dp/B08K36VLZ2/ref=sxin_16_pa_sp_search_thematic_sspa?content-id=amzn1.sym.679ca254-fe3a-4b5e-a742-a17f009c74a4%3Aamzn1.sym.679ca254-fe3a-4b5e-a742-a17f009c74a4&crid=1JZ1KFH3FN9X6&cv_ct_cx=reed+switch&dib=eyJ2IjoiMSJ9.Y7tGh1OJja6-m64Rw-v8qapQvrMcIXHucwIJBA01_WDe4VlQMcLYXAlOXbptsaJd3yBK-R5Nr9z3JzvJNUFYDQ.jbqtepN4LySsN7OK7JE0aqdm5gARa70sTdvbInD-PTY&dib_tag=se&keywords=reed+switch&pd_rd_i=B08K36VLZ2&pd_rd_r=57d03c4e-74ee-4abb-abb3-c25d8ddb869f&pd_rd_w=E0daE&pd_rd_wg=0U2Kd&pf_rd_p=679ca254-fe3a-4b5e-a742-a17f009c74a4&pf_rd_r=WA89C7K188JMW5TZWHC5&qid=1716603028&s=electronics&sbo=RZvfv%2F%2FHxDF%2BO5021pAnSA%3D%3D&sprefix=red+switch%2Celectronics%2C130&sr=1-1-364cf978-ce2a-480a-9bb0-bdb96faa0f61-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9zZWFyY2hfdGhlbWF0aWM&psc=1] |
| 1 | | 1 | ||
− | | $ | + | | $7.90 |
|- | |- | ||
− | ! scope="row"| | + | ! scope="row"| 11 |
| DB9 connector female | | DB9 connector female | ||
| Amazon [https://www.amazon.com/Anmbest-Solderless-Terminal-Connector-Breakout/dp/B07S7M24C3/ref=sr_1_5?crid=1Y45MDO47UIPU&dib=eyJ2IjoiMSJ9.yXkwsWep8-KzcZ8p5NDCkDV0LWHWMA9X0KnjVaIJ_6nE4GzQir7zAhOEGs-1OEwDToreCS1sZo-vuG1_JXVVZZSOX01Z4Vc-M8D6M4it4PRw4dZ1sifbJt6MyMYHRLku934VbPlnrmdW-VcNwzbx89ileq2DH3xpKAEiT6RlDeKdsdAx1fluAt0FxaJGERt3gxeR3dRRoUZz6nm_l28_hkDLM4BdUm4AsWNBvM_zsUs.qYTq2vzDt_eh8eFTakd4jH8Ofmug-sY32k0OqeQp_pM&dib_tag=se&keywords=DB9%2Bconnector&qid=1709108972&sprefix=db9%2Bconnector%2Caps%2C325&sr=8-5&th=1] | | Amazon [https://www.amazon.com/Anmbest-Solderless-Terminal-Connector-Breakout/dp/B07S7M24C3/ref=sr_1_5?crid=1Y45MDO47UIPU&dib=eyJ2IjoiMSJ9.yXkwsWep8-KzcZ8p5NDCkDV0LWHWMA9X0KnjVaIJ_6nE4GzQir7zAhOEGs-1OEwDToreCS1sZo-vuG1_JXVVZZSOX01Z4Vc-M8D6M4it4PRw4dZ1sifbJt6MyMYHRLku934VbPlnrmdW-VcNwzbx89ileq2DH3xpKAEiT6RlDeKdsdAx1fluAt0FxaJGERt3gxeR3dRRoUZz6nm_l28_hkDLM4BdUm4AsWNBvM_zsUs.qYTq2vzDt_eh8eFTakd4jH8Ofmug-sY32k0OqeQp_pM&dib_tag=se&keywords=DB9%2Bconnector&qid=1709108972&sprefix=db9%2Bconnector%2Caps%2C325&sr=8-5&th=1] | ||
Line 274: | Line 277: | ||
| $9.99 | | $9.99 | ||
|- | |- | ||
− | ! scope="row"| | + | ! scope="row"| 12 |
| Jumper wires | | Jumper wires | ||
| Amazon [https://www.amazon.com/dp/B07GD2BWPY?ref_=cm_sw_r_apan_dp_CFJEVX2TRBD3200XQXX3&language=en-US] | | Amazon [https://www.amazon.com/dp/B07GD2BWPY?ref_=cm_sw_r_apan_dp_CFJEVX2TRBD3200XQXX3&language=en-US] | ||
Line 280: | Line 283: | ||
| $6.98 each | | $6.98 each | ||
|- | |- | ||
− | ! scope="row"| | + | ! scope="row"| 13 |
− | | | + | | Prototyping Board |
− | | Amazon [] | + | | Amazon [https://www.amazon.com/LampVPath-Prototype-Breadboard-Universal-Printed/dp/B07Y3FDDMB/ref=sr_1_1?crid=R7BANENOVRQW&dib=eyJ2IjoiMSJ9.svnA16lUFrNDMnHctaHAEAQ6NJjQ-B1vsrRU9ONBnzvm2ueCSFtl5OXsWFBV30kP1kO94U1TCnjStCMXo3rQR2MQDo-9ooO-tG0rNXfVqlx68qRu1cz3SjwydkqipjpV9GOVCTf3fuvcb6I7ZlbmWkZXa4jcEm5ohaL1z7wdWL0.zBHHL7hkTB64WLSVTKvYfeQt4rIcd8l2O7effsBZWEw&dib_tag=se&keywords=prototyping%2Bboard%2BlampVPath&qid=1716603189&s=electronics&sprefix=prototyping%2Bboard%2Blampvpath%2Celectronics%2C118&sr=1-1-catcorr&th=1] |
| 2 | | 2 | ||
− | | $ | + | | $7.90 |
|} | |} | ||
Line 291: | Line 294: | ||
== Printed Circuit Board == | == Printed Circuit Board == | ||
− | + | To handle the sheer complexity of connections, a custom PCB was considered as early as the prototype 1. Designed in KiCAD, from schematic capturing to PCB layout. | |
− | + | ||
+ | |||
+ | Complex wiring. | ||
+ | [[File:Top_view.JPG|400px]] | ||
+ | |||
+ | Geo node schematic. | ||
+ | [[File:Geosch.jpg|400px]] | ||
+ | |||
+ | Motor node schematic. | ||
+ | [[File:Motorsch.jpg|400px]] | ||
+ | |||
+ | Sensor node schematic. | ||
+ | [[File:Sensorsch.jpg|400px]] | ||
+ | |||
+ | Driver node schematic. | ||
+ | [[File:Driversch.jpg|400px]] | ||
+ | |||
+ | == Challenges == | ||
+ | |||
+ | We found ourselves going back and forth with soldering and adjusting connections, therefore we opted for a more robust PCB based design. | ||
+ | |||
+ | == Design Steps == | ||
+ | *Identify desired mounting topology and make measurements | ||
+ | *Use a CAD software to capture schematic | ||
+ | *Define a PCB size and place mounting holes | ||
+ | *Plan PCB layout and start routing | ||
+ | *Check for ERC and DRC errors | ||
+ | *Get design files ready for manufacturing | ||
+ | |||
+ | [[File:Pcb1.jpg|500px|center|thumb|PCB Schematic]][[File:3db.jpg|500px|center|thumb|PCB Schematic BOTTOM]][[File:3dt.jpg|500px|center|thumb|PCB Schematic TOP]] | ||
+ | |||
<HR> | <HR> | ||
<BR/> | <BR/> | ||
+ | |||
== CAN Communication == | == CAN Communication == | ||
− | |||
=== Hardware Design === | === Hardware Design === | ||
− | + | [[File:CAN.jpg|500px|center|thumb|CAN BUS]] | |
=== DBC File === | === DBC File === | ||
− | + | DBC file [https://gitlab.com/minalupadhye/C243_terrabyte/-/blob/master/dbc/project.dbc?ref_type=heads] | |
+ | |||
+ | <syntaxhighlight lang="text"> | ||
+ | VERSION "Final" | ||
+ | |||
+ | NS_ : | ||
+ | BA_ | ||
+ | BA_DEF_ | ||
+ | BA_DEF_DEF_ | ||
+ | BA_DEF_DEF_REL_ | ||
+ | BA_DEF_REL_ | ||
+ | BA_DEF_SGTYPE_ | ||
+ | BA_REL_ | ||
+ | BA_SGTYPE_ | ||
+ | BO_TX_BU_ | ||
+ | BU_BO_REL_ | ||
+ | BU_EV_REL_ | ||
+ | BU_SG_REL_ | ||
+ | CAT_ | ||
+ | CAT_DEF_ | ||
+ | CM_ | ||
+ | ENVVAR_DATA_ | ||
+ | EV_DATA_ | ||
+ | FILTER | ||
+ | NS_DESC_ | ||
+ | SGTYPE_ | ||
+ | SGTYPE_VAL_ | ||
+ | SG_MUL_VAL_ | ||
+ | SIGTYPE_VALTYPE_ | ||
+ | SIG_GROUP_ | ||
+ | SIG_TYPE_REF_ | ||
+ | SIG_VALTYPE_ | ||
+ | VAL_ | ||
+ | VAL_TABLE_ | ||
+ | |||
+ | BS_: | ||
+ | |||
+ | BU_: DBG DRIVER MOTOR SENSOR_BRIDGE GEO | ||
+ | |||
+ | |||
+ | BO_ 50 APP_COMMAND: 1 SENSOR_BRIDGE | ||
+ | SG_ BRIDGE_START_STOP : 0|1@1+ (1,0) [0|0] "Bool" DRIVER | ||
+ | |||
+ | BO_ 99 DRIVER_HEARTBEAT: 1 DRIVER | ||
+ | SG_ DRIVER_HEARTBEAT_cmd : 0|8@1+ (1,0) [0|0] "" SENSOR_BRIDGE,MOTOR | ||
+ | |||
+ | BO_ 100 SENSOR_DATA: 4 SENSOR_BRIDGE | ||
+ | SG_ SENSOR_DATA_left : 0|8@1+ (1,0) [0|0] "cm" DRIVER | ||
+ | SG_ SENSOR_DATA_right : 8|8@1+ (1,0) [0|0] "cm" DRIVER | ||
+ | SG_ SENSOR_DATA_front : 16|8@1+ (1,0) [0|0] "cm" DRIVER | ||
+ | SG_ SENSOR_DATA_rear : 24|8@1+ (1,0) [0|0] "cm" DRIVER | ||
+ | |||
+ | BO_ 200 GEO_STATUS: 8 GEO | ||
+ | SG_ GEO_STATUS_COMPASS_HEADING : 0|12@1+ (1,0) [0|359] "Degrees" DRIVER, SENSOR_BRIDGE | ||
+ | SG_ GEO_STATUS_COMPASS_BEARING : 12|12@1+ (1,0) [0|359] "Degrees" DRIVER, SENSOR_BRIDGE | ||
+ | SG_ GEO_STATUS_DISTANCE_TO_DESTINATION : 24|32@1+ (0.1,0) [0|0] "Meters" DRIVER, SENSOR_BRIDGE | ||
+ | SG_ GEO_STATUS_VALID_FLAG: 56|1@1+ (1,0) [0|1] "Bool" DRIVER, SENSOR_BRIDGE | ||
+ | SG_ GEO_STATUS_DESTINATION_REACHED: 57|1@1+ (1,0) [0|1] "Bool" DRIVER, SENSOR_BRIDGE | ||
+ | |||
+ | BO_ 250 GPS_CURRENT_LOCATION: 8 GEO | ||
+ | SG_ GPS_CURR_LATITUDE_SCALED_1000000 : 0|32@1- (1,0) [-90000000|90000000] "Degrees" SENSOR_BRIDGE | ||
+ | SG_ GPS_CURR_LONGITUDE_SCALED_1000000 : 32|32@1- (1,0) [-180000000|180000000] "Degrees" SENSOR_BRIDGE | ||
+ | |||
+ | BO_ 300 DRIVER_CONTROL: 2 DRIVER | ||
+ | SG_ DRIVER_CONTROL_steer : 0|3@1- (1,0) [-2|2] "" MOTOR | ||
+ | SG_ DRIVER_CONTROL_speed : 3|4@1- (1,0) [-1|2] "kph" MOTOR | ||
+ | |||
+ | BO_ 400 MOTOR_STATUS: 2 MOTOR | ||
+ | SG_ MOTOR_STATUS_wheel_speed : 0|12@1+ (0.1,-70) [-70|70] "kph" SENSOR_BRIDGE | ||
+ | |||
+ | BO_ 500 GPS_DESTINATION_LOCATION: 8 SENSOR_BRIDGE | ||
+ | SG_ GPS_DEST_LATITUDE_SCALED_1000000 : 0|32@1- (1,0) [-90000000|90000000] "Degrees" GEO | ||
+ | SG_ GPS_DEST_LONGITUDE_SCALED_1000000 : 32|32@1- (1,0) [-180000000|180000000] "Degrees" GEO | ||
+ | |||
+ | BO_ 600 DEBUG_DRIVER: 2 DRIVER | ||
+ | SG_ DRIVER_OA_or_W : 0|1@1+ (1,0) [0|0] "Bool" DBG | ||
+ | SG_ DRIVER_OA_state : 1|8@1+ (1,0) [0|0] "Char" DBG | ||
+ | |||
+ | BO_ 602 DEBUG_MOTOR: 3 MOTOR | ||
+ | SG_ MOTOR_PCTRLR_PWM_debug : 0|7@1+ (0.1,0) [10|20] "%" DBG | ||
+ | SG_ MOTOR_ROTATIONSPER2S_debug : 7|12@1+ (1,0) [0|0] "" DBG | ||
+ | |||
+ | BO_ 603 DEBUG_GEO: 1 GEO | ||
+ | SG_ GEO_Compass_heartbeat : 0|1@1+ (1,0) [0|0] "Bool" DBG | ||
+ | |||
+ | CM_ BU_ DRIVER "The driver controller driving the car"; | ||
+ | CM_ BU_ MOTOR "The motor controller of the car"; | ||
+ | CM_ BU_ SENSOR "The sensor controller of the car"; | ||
+ | CM_ BO_ 100 "Sync message used to synchronize the controllers"; | ||
+ | CM_ SG_ 100 DRIVER_HEARTBEAT_cmd "Heartbeat command from the driver"; | ||
+ | |||
+ | BA_DEF_ "BusType" STRING ; | ||
+ | BA_DEF_ BO_ "GenMsgCycleTime" INT 0 0; | ||
+ | BA_DEF_ SG_ "FieldType" STRING ; | ||
+ | |||
+ | BA_DEF_DEF_ "BusType" "CAN"; | ||
+ | BA_DEF_DEF_ "FieldType" ""; | ||
+ | BA_DEF_DEF_ "GenMsgCycleTime" 0; | ||
+ | |||
+ | BA_ "GenMsgCycleTime" BO_ 100 1000; | ||
+ | BA_ "GenMsgCycleTime" BO_ 200 50; | ||
+ | BA_ "FieldType" SG_ 100 DRIVER_HEARTBEAT_cmd "DRIVER_HEARTBEAT_cmd"; | ||
+ | |||
+ | VAL_ 100 DRIVER_HEARTBEAT_cmd 2 "DRIVER_HEARTBEAT_cmd_REBOOT" 1 "DRIVER_HEARTBEAT_cmd_SYNC" 0 "DRIVER_HEARTBEAT_cmd_NOOP" ; | ||
+ | |||
+ | </syntaxhighlight> | ||
<HR> | <HR> | ||
Line 309: | Line 447: | ||
== Sensor Controller & Bridge == | == Sensor Controller & Bridge == | ||
− | + | There are 4 sensors fitted on the car: Front, Rear, Left and Right. | |
=== Hardware Design === | === Hardware Design === | ||
+ | |||
=== Software Design === | === Software Design === | ||
− | + | ||
=== Technical Challenges === | === Technical Challenges === | ||
− | |||
<HR> | <HR> | ||
Line 324: | Line 462: | ||
== Driver Controller & LCD == | == Driver Controller & LCD == | ||
− | + | The LCD is a GeekPi 20x4 display using I2C. LCD displays some critical information during runtime which is critical for debugging. This information is split into 4 lines. | |
+ | * Line1 displays sensor data from 4 sensors in the sequence Back(Rear), Left, Front, Right. | ||
+ | * Line2 displays command to motor from driver which are two signals: the speed (forward, stop, reverse) and steer values (left, right, straight). | ||
+ | * Lines 3 and 4 display information related to Geo module. Line 3 displays distance to destination and whether the current coordinates are valid (valid flag). | ||
+ | * Line4 displays current heading and bearing values from compass. | ||
+ | |||
+ | [[File:Lcd_terrabyte.jpeg|500px]] | ||
+ | |||
=== Hardware Design === | === Hardware Design === | ||
+ | [[File:driver_layout.JPG|500px]] | ||
+ | |||
The SJ2 board hosting the driver module is connected to - | The SJ2 board hosting the driver module is connected to - | ||
− | * CAN bus | + | * CAN Transceiver |
− | * LCD | + | SJ2 is connected to CAN bus through the CAN transceiver as shown in the diagram. |
− | * Sensor LEDs | + | * LCD |
+ | LCD is powered by 5V input from the power bank and a common ground with SJ2. LCD is connected to SJ2 using I2C, hence the SDA and SCL pins that transfer data to be printed. | ||
+ | * Sensor LEDs | ||
+ | There is one LED per sensor i.e. Front, Rear, Left and Right which are connected to GPIO pins on SJ2. | ||
+ | [[File:Sensor_LEDs.jpeg|200px]] | ||
=== Software Design === | === Software Design === | ||
Line 338: | Line 489: | ||
* Display important information on the LCD. | * Display important information on the LCD. | ||
− | Driver_logic() is the core part which manages the entire module. | + | Driver_logic() is the core part which manages the entire module. API obstacle_avoidance() takes sensor values as input and decides the direction and speed to move. Similarly, API waypoint_algo() takes geo input and decides the direction and speed to move. |
+ | |||
+ | Driver module decides which one to follow in every iteration of the periodic function it is scheduled. While it is important to move towards destination and follow geo inputs, obstacle avoidance has priority over it. | ||
+ | |||
+ | ===== Driver Logic ===== | ||
+ | |||
+ | if (obstacle_avoidance_flag) { | ||
+ | motor_control_to_transmit = obstacle_avoidance(); | ||
+ | } else { | ||
+ | motor_control_to_transmit = waypoint_algo(received_geo_status); | ||
+ | } | ||
+ | |||
+ | ===== Command to motor (Steer) ===== | ||
+ | |||
+ | typedef enum { | ||
+ | HARD_LEFT = -2, | ||
+ | SOFT_LEFT, | ||
+ | NO_STEER, | ||
+ | SOFT_RIGHT, | ||
+ | HARD_RIGHT, | ||
+ | } steer__val_e; | ||
+ | |||
+ | Obstacle Avoidance is basically a state machine with three states - Forward, Stop and Reverse. The default state is Stop and then based on sensor inputs, the state transitions are made. | ||
+ | |||
+ | ===== Obstacle Avoidance states ===== | ||
+ | |||
+ | static void stop_state(void) { | ||
+ | sensor_based_motor_command = move_stop(); | ||
+ | if (front_sensor_cm < FRONT_STOP_THRESHOLD) { | ||
+ | OA_state = 'R'; | ||
+ | } else { | ||
+ | OA_state = 'F'; | ||
+ | } | ||
+ | } | ||
+ | static void reverse_state(void) { | ||
+ | if (front_sensor_cm < FRONT_STOP_THRESHOLD) { | ||
+ | sensor_based_motor_command = move_reverse(); | ||
+ | } else { | ||
+ | OA_state = 'S'; | ||
+ | } | ||
+ | } | ||
+ | static void forward_state(void) { | ||
+ | if (front_sensor_cm < FRONT_STOP_THRESHOLD) { | ||
+ | OA_state = 'S'; | ||
+ | } else if ((left_sensor_cm < SIDE_SOFT_THRESHOLD) || right_sensor_cm < SIDE_SOFT_THRESHOLD) { | ||
+ | if (left_sensor_cm < right_sensor_cm) { | ||
+ | process_left_sensor(); | ||
+ | } else { | ||
+ | process_right_sensor(); | ||
+ | } | ||
+ | } else { | ||
+ | sensor_based_motor_command = move_forward_slow(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | Waypoint algorithm takes Compass heading and bearing as input and decides the direction to move. This is achieved by calculating the angle required to turn and further the optimized angle. | ||
+ | |||
+ | ===== Waypoint angle calculation ===== | ||
+ | |||
+ | static int16_t calculate_angle(uint16_t heading, uint16_t bearing) { | ||
+ | int16_t angle; | ||
+ | angle = bearing - heading; | ||
+ | if (angle > 180) { | ||
+ | angle = angle - 360; | ||
+ | } else if (angle < -180) { | ||
+ | angle = angle + 360; | ||
+ | } | ||
+ | return angle; | ||
+ | } | ||
− | + | There are a few more conditions around which the driver module operates: | |
+ | * Initially, the car will only start moving if receives a START indication from the app. | ||
+ | * The car will stop whenever a STOP is indicated from the app. | ||
+ | * The car will stop if the destination is reached. | ||
− | + | ===== App Commands ===== | |
− | + | ||
− | + | if (app_cmd_received.BRIDGE_START_STOP == true) { | |
− | + | driver__process_input(); | |
− | + | driver__process_geo_controller_directions(); | |
+ | } else { | ||
+ | motor_control_to_transmit = move_stop(); | ||
+ | } | ||
} | } | ||
+ | |||
+ | Apart from this, the sensor LEDs are also controlled by driver module. The sensor LEDs glow if an obstacle is detected by a particular sensor based on the specified threshold. | ||
+ | |||
+ | ===== Sensor LEDs ===== | ||
+ | |||
+ | if (front_sensor_cm < FRONT_STOP_THRESHOLD) { | ||
+ | set_led_front(); | ||
+ | } else { | ||
+ | reset_led_front(); | ||
+ | } | ||
+ | if (rear_sensor_cm < REAR_THRESHOLD) { | ||
+ | set_led_rear(); | ||
+ | } else { | ||
+ | reset_led_rear(); | ||
+ | } | ||
+ | if (left_sensor_cm < SIDE_SOFT_THRESHOLD) { | ||
+ | set_led_left(); | ||
+ | } else { | ||
+ | reset_led_left(); | ||
+ | } | ||
+ | if (right_sensor_cm < SIDE_SOFT_THRESHOLD) { | ||
+ | set_led_right(); | ||
+ | } else { | ||
+ | reset_led_right(); | ||
+ | } | ||
=== Technical Challenges === | === Technical Challenges === | ||
− | + | * LCD delays caused SJ2 microcontroller reset. | |
+ | LCD requires delays to work properly. Calling the LCD_API in periodics reset the SJ2 microcontroller. To solve this problem, a separate task (not in periodics) in main() was created with a separate stack which resolved the problem. | ||
<HR> | <HR> | ||
Line 358: | Line 610: | ||
== Motor Controller == | == Motor Controller == | ||
− | |||
=== Hardware Design === | === Hardware Design === | ||
− | We used Traaxas Slash XL5 RWD for this project. The ESC (XL-5), BLDC motor, servo and battery come with the RC car. The 6V | + | The motor controller, is the SJ2 board linked to powertrain, wheel encoder and CAN transceiver, which manages the motor's operation. It follows the direction and speed instructions provided by the Driver module. Additionally, it measures the wheel speed and maintains the preset speed across different terrains. This wheel speed data is transmitted to the Sensor-Bridge module for display on the Android App. |
+ | <br>[[File:Motor_Control_Overview.png]] | ||
+ | |||
+ | ==== Powertrain==== | ||
+ | |||
+ | We used <b> Traaxas Slash XL5 RWD </b> for this project. The ESC (XL-5), BLDC motor, servo motor and battery come with the RC car. The 6V 3000mAh NiMH battery powers the ESC (XL-5), servo and BLDC motor. The ESC controls the BLDC however the servo is not connected via the ESC. The BLDC controls the forward, reverse and brake motion of the car along with the respective speed in the forward and reverse direction. The servo controls the steer angle. Brake and speed in both forward and reverse in case of BLDC motor and steer angle in case of Servo motor are controlled by PWM signals. | ||
<br> The ESC operates in 3 different modes as per the user manual, we choose to operate the car in Training mode where the forward and reverse speeds are half its original speeds. The maximum speed the car could reach is around 30Kmph in this mode. This mode can be set by long pressing the button on the ESC and releasing the button when there are 3 consecutive red blinks. | <br> The ESC operates in 3 different modes as per the user manual, we choose to operate the car in Training mode where the forward and reverse speeds are half its original speeds. The maximum speed the car could reach is around 30Kmph in this mode. This mode can be set by long pressing the button on the ESC and releasing the button when there are 3 consecutive red blinks. | ||
+ | <br> For the RC car the PWM signals are provided by the Radio Transmitter + Remote. We tried to understand the sequence of PWM that is to be provided to keep the ESC calibrated, for forward, reverse and brake by providing the commands on the remote and checking the waveform on oscilloscope. | ||
+ | <br>[[File:Understanding_PWM_Sequence.png]] | ||
+ | |||
+ | <br>The following were our observations which helped us develop motor control module: | ||
+ | |||
+ | {| class="wikitable" | ||
+ | |||
+ | |- | ||
+ | ! STATE !! PWM !! COMMENTS | ||
+ | |- | ||
+ | | ESC Calibration || 15% || | ||
+ | * ESC is calibrated – Solid red LED | ||
+ | * ESC out of calibration - Blinking green LED | ||
+ | |- | ||
+ | | Forward || 15.1% to 20% || Speed increases with the PWM dutycycle | ||
+ | |- | ||
+ | | Brake || | ||
+ | * Hard brake: PWM < 15% followed by 15%. | ||
+ | * Soft brake: 15%. | ||
+ | |- | ||
+ | | Reverse || 10% to 14.9% || Speed increases with the decrease in PWM dutycycle | ||
+ | |- | ||
+ | | Right || 15.1% to 20% || Angle increases with the PWM dutycycle | ||
+ | |- | ||
+ | | Left || 10% to 14.9% || Angle increases with decrease in PWM dutycycle | ||
+ | |- | ||
+ | | No steer || 15% || | ||
+ | |} | ||
+ | |||
+ | ==== Wheel Encoder ==== | ||
+ | |||
+ | We opted reed switch for wheel encoding. | ||
+ | <br> <b> Placement </b>: The magnet, along with its holder, was positioned on the spur gear, while the reed switch was housed within the casing covering the spur gear. The reed switch is connected between GPIO pin and ground. (NOTE: All GPIO pins of SJ2 board are pulled up to 3.3 V). | ||
+ | <br> The reed switch is usually open (giving a 3.3V when observed on oscilloscope) but when it crosses the magnet the reed switch closes giving a 0V. The number of edge falls in a minute gives the rpm. This is converted into linear speed by considering the circumference of wheel and gear to wheel ratio (since the wheel encoder was placed in the spur gear every 2.73 rotations of spur gear yields 1 rotation of wheel). | ||
+ | <br>[[File:SpurGear_Terrabyte.png]] [[File:Reed.png]] | ||
=== Software Design === | === Software Design === | ||
− | + | ||
+ | Motor Controller has the following code modules in periodics: | ||
+ | * Initialization | ||
+ | ** CAN Initialization | ||
+ | *** Configure GPIO pins P0.0 and P0.1 for CAN Rx and Tx respectively. | ||
+ | *** Set the baud rate for CAN communication. | ||
+ | *** Configure the CAN Tx and Rx buffer sizes. | ||
+ | *** Enable CAN reset in case of bus off. | ||
+ | *** Bypass the CAN software filter. | ||
+ | *** Set PWM on P2.0 as 15% and set a delay for 3s for ESC calibration | ||
+ | ** PWM Initialization | ||
+ | *** Configure GPIO pins P2.0 and P2.1 for PWM functionality. | ||
+ | *** Set the PWM frequency to 100 Hz. | ||
+ | *** Set the PWM dutycycle on P2.0 as 15% and a delay of 3s for ESC calibration | ||
+ | ** Interrupt Initialization | ||
+ | *** Configure GPIO pin P0.6 as an input. | ||
+ | *** Enable interrupts on both rising and falling edges for this pin. | ||
+ | *** Define the interrupt service routine. | ||
+ | * 1Hz Tasks | ||
+ | ** Calculate Wheel Speed | ||
+ | *20Hz Tasks | ||
+ | ** Incoming CAN Message Handling:Process incoming Speed Level and Steer Angle messages from the Driver module. Handle incoming CAN messages appropriately along with Mia Handling. | ||
+ | ** Motor Controller: Execute the main function of the motor controller. | ||
+ | ** CAN Transmission: Transmit the wheel speed over CAN. | ||
+ | |||
+ | ==== Calculate Wheel Speed ==== | ||
+ | The number of falling edges recorded on P0.6 in 1 minute yields rpm of the spur gear. The function to calculate wheel speed translates the rpm of spur gear into wheel speed. (Note: This function runs every 1s though the naming indicates 0.5Hz). Once the speed is calculated the number of rotations of the spur gear that is accumulated is cleared. | ||
+ | void calcuate_speed_0_5Hz(void) { | ||
+ | const float wheel_circumference = 34.54; // in cms | ||
+ | const float gear_ratio = 2.73; | ||
+ | speed = 3600 * rotations_cnt * wheel_circumference / (1000 * 100 * gear_ratio); | ||
+ | rotations_cnt = 0; | ||
+ | } | ||
+ | |||
+ | ==== Main Motor Controller Function ==== | ||
+ | |||
+ | Flowchart of the Main Motor Controller functionality is depicted below. The speed level and steer angle commands are received from Driver module via CAN as indicated below. | ||
+ | <br> [[File:MotorControl_sw_overview.png]] <br> | ||
+ | |||
+ | ====== Forward Reverse or Brake Helper Function ====== | ||
+ | Flowchart for the helper function to decide if the car has to move forward, reverse or brake is shown below. | ||
+ | <br> In the forward direction, a P controller is activated to uphold the preset speed regardless of the terrain. The speed inputted by the driver module is in speed levels, which are then translated into target speeds in kilometers per hour (km/h) for the P controller's operation. However, in reverse and during braking, the PWM sequence remains constant. | ||
+ | |||
+ | <br> [[File:Motor_Forward_Brake_reverse.png]] | ||
+ | |||
+ | <b> Speed Level Converter </b>: | ||
+ | Speed Level from Driver Module converted to Target speed in Km/h | ||
+ | float speedlevel_to_targetspeed(int8_t speed_level) { | ||
+ | float target_speed; | ||
+ | switch (speed_level) { | ||
+ | case 1: | ||
+ | target_speed = 3; // in kmph | ||
+ | break; | ||
+ | case 2: | ||
+ | target_speed = 5; // in kmph | ||
+ | break; | ||
+ | default: | ||
+ | target_speed = 3; // in kmph | ||
+ | break; | ||
+ | } | ||
+ | return target_speed; | ||
+ | } | ||
+ | |||
+ | |||
+ | <b> P Controller </b>: | ||
+ | The P controller is set different for positive and negative errors, this was done so that the speed is quickly decreased after climbing a ramp. However this is not required for flat surfaces. The maximum pwm output from P controller is se3t to 16.8% dutycycle and minimum PWM is set to 16% dutycycle. | ||
+ | float p_Ctrlr(float dutycycle, float target_speed, float current_speed) { | ||
+ | float err; | ||
+ | float Kp = 0; | ||
+ | const float Kp_f = 0.01; | ||
+ | const float Kp_r = 0.1; | ||
+ | const float pwm_min_forward = 16; | ||
+ | const float pwm_max_forward = 16.8; | ||
+ | err = target_speed - current_speed; | ||
+ | if (err > 0.5) { | ||
+ | Kp = Kp_f; | ||
+ | } else if (err < -0.5) { | ||
+ | Kp = Kp_r; | ||
+ | } else { | ||
+ | Kp = 0; | ||
+ | } | ||
+ | dutycycle = min(max(((err * Kp) + dutycycle), pwm_min_forward), pwm_max_forward); | ||
+ | return dutycycle; | ||
+ | } | ||
+ | |||
+ | ====== Steer Helper Function ====== | ||
+ | Below is the flowchart depicting the Steer Helper function. Depending on the driver's steer angle command, the car is capable of executing sharp or gentle turns to the left or right, or maintaining a straight trajectory with no steering input. | ||
+ | |||
+ | <br> [[File:Motor_steer.png]] | ||
=== Technical Challenges === | === Technical Challenges === | ||
− | < | + | 1. <b>Issue:</b> The car occasionally engaged in reverse and failed to do so at other times. <br> |
+ | <b> Cause:</b> To streamline the code, the brake state was only activated during the transition from forward to reverse; otherwise, a PWM <15% was directly applied. <br> | ||
+ | <b> Solution:</b> Always adhere to the reverse sequence: Brake-Neutral-Reverse.<br> | ||
+ | |||
+ | 2. <b>Issue:</b> After implementing the P controller, the car moved in reverse (at high speed) instead of forward. <br> | ||
+ | <b> Cause: </b> | ||
+ | ** The calculated speed was excessively high due to noise in the reed switch during transition from OFF to ON, resulting in more edge falls than expected, leading to an overly high calculated wheel speed. <br> | ||
+ | [[File:Reed issue.png]] | ||
+ | ** Placing the magnet on the spur gear caused the reed switch to count the rotations of the spur gear, necessitating consideration of the spur gear-to-wheel rotation ratio. <br> | ||
+ | |||
+ | <b> Solution:</b> | ||
+ | ** To mitigate noise, edge fall counting was enabled only if the subsequent edge rise occurred beyond 700 microseconds. | ||
+ | ** The gear-to-wheel ratio was taken into account. | ||
+ | ** Additionally, maximum and minimum limits of 16.8 and 16, respectively, were imposed on the P controller output to prevent the RC car from moving excessively fast or too slowly. | ||
+ | |||
<HR> | <HR> | ||
Line 376: | Line 769: | ||
== Geographical Controller == | == Geographical Controller == | ||
− | + | ||
+ | The geo controller has a dedicated SJ2 board attached to the CAN bus via a transceiver. The main task of the geo controller is to provide the driver node with directions to the destination location. | ||
+ | |||
+ | The geo node is attached with GPS and compass modules for this task. | ||
=== Hardware Design === | === Hardware Design === | ||
+ | |||
+ | The following image shows the hardware connections of the GEO node | ||
+ | |||
+ | [[File:GEO_HW.png|700x700px|alt=Description of the image]] | ||
+ | |||
+ | ===== GPS Module ===== | ||
+ | |||
+ | |||
+ | |||
+ | The GPS module used is an Adafruit Breakout V3, PA1616S | ||
+ | <br> | ||
+ | <br> | ||
+ | [[File:GPS_TerraByte.jpg|490x400px|alt=Adafruit GPS stock]] | ||
+ | [[File:Cmps_cropped_TerraByte.jpg|400x400px|alt=RoboTech CMPS12 stock]] | ||
+ | <br> | ||
+ | <br> | ||
+ | *It is a UART-based module that runs at 9600bps by default and can be configured up to 115200bps. | ||
+ | *It is attached to the SJ2 board on UART3 on pins P4.28(TX3) and P4.29(RX3). | ||
+ | *The TX is connected to the RX of the GPS and the RX pin is connected to the TX of the GPS. | ||
+ | *The GPS module outputs various NMEA strings like $GPGLL, $GPGSA etc… out of which we are concerned with only 1 type of string which is the $GPGGA - Global Positioning Systems Fix Data. | ||
+ | * Once the GPS gets a fix, the fix LED on the module will start blinking every 15 seconds. | ||
+ | * a button cell CR1220 is added to the GPS module to hotstart the module and get a fix faster | ||
+ | <br> | ||
+ | |||
+ | The $GPGGA string is of the format: | ||
+ | *$GPGGA,HHMMSS.SS,DDMM.MMMMM,K,DDDMM.MMMMM,L,N,QQ,PP.P,AAAA.AA,M,±XX.XX,M, SSS,RRRR*CC<CR><LF> | ||
+ | |||
+ | |||
+ | {| class="wikitable" | ||
+ | ! Name | ||
+ | ! Example | ||
+ | ! Description | ||
+ | |- | ||
+ | | Message ID | ||
+ | | $GPGGA | ||
+ | | GGA protocol header | ||
+ | |- | ||
+ | | UTC Time | ||
+ | | 002153.000 | ||
+ | | hhmmss.sss | ||
+ | |- | ||
+ | | Latitude | ||
+ | | 3342.6618 | ||
+ | | dddmm.mmmm | ||
+ | |- | ||
+ | | N/S indicator | ||
+ | | N | ||
+ | | North or South Indicator | ||
+ | |- | ||
+ | | Longitude | ||
+ | | 11751.3858 | ||
+ | | dddmm.mmmm | ||
+ | |- | ||
+ | | E/W Indicator | ||
+ | | W | ||
+ | | East or West Indicator | ||
+ | |- | ||
+ | | Position Fix Indicator | ||
+ | | 1 | ||
+ | | Indicates if there is a satellite fix | ||
+ | |- | ||
+ | | Satellite Used | ||
+ | | 10 | ||
+ | | Range 0 to 12 | ||
+ | |- | ||
+ | | HDOP | ||
+ | | 1.2 | ||
+ | | Horizontal Dilution of Precision | ||
+ | |- | ||
+ | | MSL Altitude | ||
+ | | 27.0 | ||
+ | | | ||
+ | |- | ||
+ | | Units | ||
+ | | M | ||
+ | | | ||
+ | |- | ||
+ | | Geoid Separation | ||
+ | | -34.2 | ||
+ | | Geoid-to-ellipsoid separation. Ellipsoid altitude = MSL Altitude + Geoid Separation | ||
+ | |- | ||
+ | | Units | ||
+ | | M | ||
+ | | | ||
+ | |- | ||
+ | | Age of Diff. Corr. | ||
+ | | | ||
+ | | Null fields when DGPS is not used | ||
+ | |- | ||
+ | | Diff. Ref. Station ID | ||
+ | | 000 | ||
+ | | | ||
+ | |- | ||
+ | | Checksum | ||
+ | | *5E | ||
+ | | | ||
+ | |- | ||
+ | | <CR><LF> | ||
+ | | | ||
+ | | End of Message termination | ||
+ | |} | ||
+ | |||
+ | *We are interested in the Message ID, latitude, N/S indicator, Longitude, E/W Indicator, and Position Fix Indicator, which the parsing algorithm will parse from the line buffer. | ||
+ | *The output will be the current location in terms of latitude and longitude of the format (3342.6618, 11751.3858). | ||
+ | |||
+ | ===== Compass ===== | ||
+ | |||
+ | '''Important Note to future students: Under no circumstances buy a compass that you have to manually calibrate like the LSM303AGR or LSM303DLHC (see technical challenges for more information)''' | ||
+ | |||
+ | <br> | ||
+ | |||
+ | *The compass module used is a CMPS12 from Bosch. It was chosen because it works out of the box, is tilt-calibrated, and does not require any additional calibration to account for the magnetic declination, soft iron, and hard iron offsets. | ||
+ | |||
+ | |||
+ | [[File:CMPS12_Terrabyte.png|300x300px|alt=CMPS12 stock]] | ||
+ | <br> | ||
+ | [[File:CMPS_on_TerraByte.jpeg|500x400px|alt=CMPS12 on TerraByte]] | ||
+ | <br> | ||
+ | |||
+ | *The compass is interfaced to the SJ2 board using the I2C_2 pins P0.10 (SDA) and P0.11 (SCL) respectively. | ||
=== Software Design === | === Software Design === | ||
− | + | ||
+ | The Geo node software was split into the following modules to efficiently handle the code: | ||
+ | |||
+ | * Can | ||
+ | ** Handles the can initialization, encode & send and the MIA configuration | ||
+ | |||
+ | * Checkpoint | ||
+ | ** The checkpoint algorithm to help the car reach the destination at a particular location | ||
+ | |||
+ | * Geo Message Bank | ||
+ | ** Handles all the sending and receiving of the CAN messages to and from the Geo node in a single place | ||
+ | |||
+ | * Geo Status | ||
+ | ** The main function used to gather all the geo node details and pack them into a CAN message | ||
+ | |||
+ | * GPS | ||
+ | ** The GPS parsing Algorithm | ||
+ | |||
+ | * LED handle | ||
+ | ** Used to handle the SJ2 Board's LEDs for debugging and indication purposes while the car is running | ||
+ | |||
+ | * Line buffer | ||
+ | ** Support Module for the GPS parser | ||
+ | |||
+ | |||
+ | ====Initialization Phase==== | ||
+ | |||
+ | *The GPS module can and should be configured to only output GPGGA strings during the initialization phase of the scheduler. This makes it easier to have a smaller line buffer and less overhead | ||
+ | |||
+ | *The command used: "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n" | ||
+ | |||
+ | *Apart from this, the GPS module along with the SJ2 UART should be raised to 115200bps to get the best possible speed when running the GPS_run_once() function at 20hz. However, we found that raising the speed to 115200 was not working. See the technical challenges section for more details. | ||
+ | |||
+ | * Very little configuration during the initialization phase is required to start getting the heading values from the compass in fact, if you are using I2C_2 by default I2C is initialized with 400khz and you don’t need to do any setup. | ||
+ | |||
+ | |||
+ | ==== Main Loop ==== | ||
+ | |||
+ | *The geo controller's main task is to send a status message ‘GEO_STATUS’ to the driver node. This frame contains details like the heading, bearing, distance to destination, and some flags to help the car reach the destination. | ||
+ | |||
+ | * All function calls CAN receive, CAN send and gps_run_once are called in 20hz frequency. | ||
+ | |||
+ | * gps_run_once() function call fetches the latest NMEA GPGGA string from the line buffer and parses it to extract the latitude and longitude along with GPS fix data. On successful parsing of the string, a debug flag is also set. If the parsing was successful and a fix was present an LED on the SJ2 board was lit up. | ||
+ | |||
+ | * The destination to which the car has to reach set from the Android app is sent by the SENSOR_BRIDGE module via CAN. | ||
+ | |||
+ | * Once the destination co-ordinate has been received the bearing and distance to the destination are computed. | ||
+ | |||
+ | * To compute the bearing we use both coordinates the current location received from the GPS module and the destination location received from the SENSOR_BRIDGE node. The values are fed into the below formula and converted into degrees from radians. | ||
+ | |||
+ | <br> | ||
+ | [[File:Bearing_formula_TerraByte.png]] | ||
+ | <br> | ||
+ | |||
+ | *To compute the distance to the destination the haversine formula is used and output is formatted into meters for better accuracy. If the distance is less than 3m a flag is set once to indicate that the final destination is reached. More on this in the technical challenges section. | ||
+ | |||
+ | <br> | ||
+ | [[File:Distance_formula_TerraByte.png]] | ||
+ | <br> | ||
+ | |||
+ | *To compute the heading the geo_status() function simply fetches the latest values from the compass and applies the constants described in the datasheet. | ||
+ | |||
+ | * Once all this data is computed the ‘GEO_STAUS’ message is populated and then sent to the driver node in the CAN transmission function call. | ||
+ | |||
+ | * A message bank module was adopted where all messages to be sent and received from the CAN are processed. | ||
+ | |||
+ | |||
+ | ==== checkpoint Algorithm ==== | ||
+ | |||
+ | The checkpoint algorithm is used to make the navigation of the car easier in the final demo location. it follows the closest checkpoints from source to destination in an effective manner | ||
+ | |||
+ | [[File:Checkpoints_TerraByte.png]] | ||
+ | |||
+ | |||
+ | * 37.339725 ,-121.881119 | ||
+ | * 37.339764 ,-121.881073 | ||
+ | * 37.339581 ,-121.880928 | ||
+ | * 37.339539 ,-121.881035 | ||
+ | * 37.339375 ,-121.880890 | ||
+ | * 37.339436 ,-121.880760 | ||
+ | * 37.339291 ,-121.880646 | ||
+ | * 37.339226 ,-121.880745 | ||
+ | * 37.339088 ,-121.880630 | ||
+ | * 37.339134 ,-121.880516 | ||
+ | * 37.338947 ,-121.880371 | ||
+ | * 37.338882 ,-121.880486 | ||
+ | |||
+ | |||
+ | * The checkpoints shown above were used as inputs to the Algorithm along with the current location and the destination location sent by the bridge node | ||
+ | |||
+ | |||
+ | * The function effectively evaluates each predefined checkpoint against the final destination to determine the closest, valid next step for navigation, optimizing for the nearest reachable point that progresses towards the final destination. It does this efficiently by precomputing distances to avoid redundant calculations during iteration. | ||
+ | |||
+ | ==== Appendix GEO ==== | ||
+ | |||
+ | |||
+ | '''Parsing Algorithm''' | ||
+ | |||
+ | <syntaxhighlight lang="c" linenumbers="true" highlight="3"> | ||
+ | static bool gps__parse_coordinates_from_line(void) { | ||
+ | char gps_line[200]; | ||
+ | bool return_value = false; | ||
+ | uint8_t field_count = 0; | ||
+ | |||
+ | |||
+ | if (line_buffer__remove_line(&line, gps_line, sizeof(gps_line))) { | ||
+ | // set co-ordinate as invalid initially and gps-fix to false | ||
+ | parsed_coordinates.valid = false; | ||
+ | gps_fix = false; | ||
+ | |||
+ | |||
+ | // copy to reparse | ||
+ | char gps_line_copy[200]; | ||
+ | strncpy(gps_line_copy, gps_line, sizeof(gps_line_copy)); | ||
+ | |||
+ | |||
+ | // parse and get the preamble | ||
+ | char *token = strtok(gps_line_copy, ","); | ||
+ | field_count++; | ||
+ | |||
+ | |||
+ | // Check if the line is a GPGGA sentence | ||
+ | if (token && strcmp(token, "$GPGGA") == 0) { | ||
+ | // Skip the time field which is the second token | ||
+ | char *time = strtok(NULL, ","); | ||
+ | field_count++; | ||
+ | |||
+ | |||
+ | // skip other fields to first check fix | ||
+ | for (int i = 0; i < 5; ++i) { | ||
+ | token = strtok(NULL, ","); | ||
+ | field_count++; | ||
+ | |||
+ | |||
+ | if (token == NULL) { | ||
+ | return_value = false; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | if (token && field_count == 7) { | ||
+ | int fix_quality = atoi(token); | ||
+ | if (fix_quality > 0) { | ||
+ | gps_fix = true; | ||
+ | turn_on_gps_fix_led(); | ||
+ | // GPS fix, parse latitiude and longititude | ||
+ | // Re-parse the original line for latitude and longitude | ||
+ | strtok(gps_line, ","); // skip $GPGGA | ||
+ | strtok(NULL, ","); // skip Time | ||
+ | char *latitude = strtok(NULL, ","); | ||
+ | char *latitude_direction = strtok(NULL, ","); | ||
+ | char *longitude = strtok(NULL, ","); | ||
+ | char *longitude_direction = strtok(NULL, ","); | ||
+ | |||
+ | |||
+ | if (latitude && longitude && latitude_direction && longitude_direction) { | ||
+ | // Convert and adjust coordinates | ||
+ | float parsed_latitude = convert_from_minutes_to_decimal(atof(latitude)); | ||
+ | float parsed_longitude = convert_from_minutes_to_decimal(atof(longitude)); | ||
+ | |||
+ | |||
+ | if (*latitude_direction == 'S') { | ||
+ | parsed_latitude *= -1.0f; | ||
+ | } | ||
+ | if (*longitude_direction == 'W') { | ||
+ | parsed_longitude *= -1.0f; | ||
+ | } | ||
+ | |||
+ | |||
+ | parsed_coordinates.latitude = parsed_latitude; | ||
+ | parsed_coordinates.longitude = parsed_longitude; | ||
+ | parsed_coordinates.valid = true; | ||
+ | return_value = true; | ||
+ | } else { | ||
+ | |||
+ | return_value = false; | ||
+ | } | ||
+ | } else { | ||
+ | turn_off_gps_fix_led(); | ||
+ | gps_fix = false; | ||
+ | return_value = false; | ||
+ | } | ||
+ | } | ||
+ | } else { | ||
+ | return_value = false; | ||
+ | } | ||
+ | } else { | ||
+ | return_value = false; | ||
+ | } | ||
+ | |||
+ | |||
+ | return return_value; | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | '''Periodic scheduler callbacks 20hz''' | ||
+ | |||
+ | <syntaxhighlight lang="c" linenumbers="true" highlight="3"> | ||
+ | void periodic_callbacks__100Hz(uint32_t callback_count) { | ||
+ | |||
+ | |||
+ | if (callback_count % 5 == 0) { | ||
+ | msg_bank__handle_msg_rx(); | ||
+ | msg_bank_geo__service_mia_10hz(); | ||
+ | gps__run_once(); | ||
+ | msg_bank__handle_msg_tx(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Geo_status function''' | ||
+ | |||
+ | <syntaxhighlight lang="c" linenumbers="true" highlight="3"> | ||
+ | void geo_get_status_2(dbc_GEO_STATUS_s *msg) { | ||
+ | float distance = 0.0f; | ||
+ | uint16_t bearing = 0; | ||
+ | uint16_t heading = 0; | ||
+ | float haversine_angle = 0.0f; | ||
+ | |||
+ | |||
+ | gps_coordinates_t gps_current_reading = gps__get_coordinates(); | ||
+ | bool geo_current_valid = gps_current_reading.valid; | ||
+ | bool geo_dest_valid = geo_get_destination_coordinates(); | ||
+ | |||
+ | |||
+ | if (geo_current_valid) { | ||
+ | if (geo_dest_valid) { | ||
+ | // Always calculate distance to final destination for the flag logic | ||
+ | haversine_angle = helper_find_haversine_angle(gps_current_reading, gps_destination); | ||
+ | distance_to_final_destination = find_distance(haversine_angle); | ||
+ | bearing = find_bearing(gps_current_reading, gps_destination); | ||
+ | |||
+ | |||
+ | #ifdef CHECKPOINT_ALGORITHM | ||
+ | // If using checkpoints, calculate nearest checkpoint and get distance to it for navigation | ||
+ | gps_coordinates_t nearest_checkpoint = | ||
+ | find_nearest_checkpoint(gps_destination, gps_current_reading, distance_to_final_destination); | ||
+ | haversine_angle = helper_find_haversine_angle(gps_current_reading, nearest_checkpoint); | ||
+ | distance = find_distance(haversine_angle); | ||
+ | bearing = find_bearing(gps_current_reading, nearest_checkpoint); | ||
+ | #else | ||
+ | // Not using checkpoints, use direct distance and bearing already calculated | ||
+ | distance = distance_to_final_destination; | ||
+ | #endif | ||
+ | } else { | ||
+ | // Use the last known good destination if new coordinates are invalid | ||
+ | haversine_angle = helper_find_haversine_angle(gps_current_reading, gps_destination); | ||
+ | distance_to_final_destination = find_distance(haversine_angle); | ||
+ | distance = distance_to_final_destination; | ||
+ | bearing = find_bearing(gps_current_reading, gps_destination); | ||
+ | } | ||
+ | |||
+ | |||
+ | // Check if the final destination is reached | ||
+ | if (distance_to_final_destination <= 1.5f) { | ||
+ | distace_zero_true = true; | ||
+ | turn_on_SJ2_LED_for_final_destination_reached(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | heading = compass2__get_heading(); | ||
+ | |||
+ | |||
+ | // Set message signals | ||
+ | msg->GEO_STATUS_DISTANCE_TO_DESTINATION = distance_to_final_destination; | ||
+ | msg->GEO_STATUS_COMPASS_HEADING = heading; | ||
+ | msg->GEO_STATUS_COMPASS_BEARING = bearing; | ||
+ | msg->GEO_STATUS_VALID_FLAG = geo_current_valid | geo_dest_valid; | ||
+ | msg->GEO_STATUS_DESTINATION_REACHED = distace_zero_true; | ||
+ | } | ||
+ | |||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Compass Data reading function''' | ||
+ | <syntaxhighlight lang="c" linenumbers="true" highlight="3"> | ||
+ | bool static compass2__read_once_magnetometer_data(void) { | ||
+ | const uint8_t COMPASS_HEADING_BYTES = 2; | ||
+ | uint8_t received_raw_data[2] = {0}; | ||
+ | compass_heartbeat = false; | ||
+ | |||
+ | |||
+ | compass_heartbeat = i2c__read_slave_data(i2c_bus_port_compass, CMPS12_ADDRESS, CMPS12_BOSCH_ANGLE_BNO055, | ||
+ | received_raw_data, COMPASS_HEADING_BYTES); | ||
+ | |||
+ | |||
+ | if (compass_heartbeat) { | ||
+ | compass_heading_degrees = ((float)(((uint16_t)received_raw_data[0] << 8) | ((uint16_t)received_raw_data[1]))) / | ||
+ | CMPS12_BOSCH_SCALING_FACTOR; | ||
+ | } | ||
+ | |||
+ | |||
+ | return compass_heartbeat; | ||
+ | } | ||
+ | |||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Checkpoint algorithm''' | ||
+ | |||
+ | <syntaxhighlight lang="c" linenumbers="true" highlight="3"> | ||
+ | gps_coordinates_t find_nearest_checkpoint(gps_coordinates_t final_destination, gps_coordinates_t current_location, | ||
+ | float distance_to_final_destination) { | ||
+ | float min_distance_to_current = FLT_MAX; | ||
+ | gps_coordinates_t nearest_checkpoint = {0}; | ||
+ | bool found_valid_checkpoint = false; | ||
+ | |||
+ | float checkpoint_to_final_distances[NUM_CHECKPOINTS]; | ||
+ | |||
+ | // pre-compute | ||
+ | for (size_t i = 0; i < NUM_CHECKPOINTS; i++) { | ||
+ | checkpoint_to_final_distances[i] = | ||
+ | find_distance(helper_find_haversine_angle(north_side_garage_checkpoints[i], final_destination)); | ||
+ | } | ||
+ | |||
+ | // Iterate through the checkpoints to find the nearest one | ||
+ | for (size_t i = 0; i < NUM_CHECKPOINTS; i++) { | ||
+ | if (checkpoint_to_final_distances[i] < distance_to_final_destination) { | ||
+ | float distance_to_checkpoint = | ||
+ | find_distance(helper_find_haversine_angle(current_location, north_side_garage_checkpoints[i])); | ||
+ | |||
+ | // Check if this checkpoint is the nearest one to the current location | ||
+ | if (distance_to_checkpoint < min_distance_to_current) { | ||
+ | min_distance_to_current = distance_to_checkpoint; | ||
+ | nearest_checkpoint = north_side_garage_checkpoints[i]; | ||
+ | found_valid_checkpoint = true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Return the final destination if no valid checkpoint is found or if the nearest checkpoint is further than the final | ||
+ | // destination | ||
+ | if (!found_valid_checkpoint || min_distance_to_current > distance_to_final_destination) { | ||
+ | return final_destination; | ||
+ | } else { | ||
+ | return nearest_checkpoint; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
=== Technical Challenges === | === Technical Challenges === | ||
− | + | The GPS module is a critical part of the RC car and has to be unit-tested and integration-tested (both tests are important equally for this node) | |
− | + | *If you are reading this then go ahead and order the CMPS12 or CMPS14 compass they are costlier but work out of the box and the code is way smaller and easier to work with. We initially bought the LSM303AGR but it gave us so many issues even after spending days to calibrate it. | |
− | < | + | <br> |
− | + | * '''Problem''': The command to set the GPS module to output only GPGGA strings was not working correctly. | |
+ | **'''Cause''': The UART peripheral takes time to get initialized. | ||
+ | **'''Solution''': Add a delay after the UART init call is made in the periodic initialization before sending the config command. | ||
− | + | *'''Problem''': Unable to switch to the higher baud rates | |
+ | **'''Cause''': Memory allocation with the line buffer | ||
+ | **'''Solution''': keep the baud rate as 9600 itself. Although this works, ideally it should be higher for the 20hz function. | ||
− | + | *'''Problem''': The parsing function was not working properly | |
+ | **'''Cause''': Without a fix, there will be empty values in the NMEA string, the algorithm did not account for this. | ||
+ | **'''Solution''': make sure to include the GPS fix parameter in the algorithm to check if the NMEA string is complete. | ||
− | + | *'''Problem''': Once the car reaches its destination and the distance to the destination becomes 0, after some time the distance will suddenly increase and the car will start moving again. | |
− | + | **'''Cause''': This is the nature of the GPS module, it keeps updating the coordinate with less precision due to math with floating point. | |
+ | **'''Solution''': Once the destination becomes 0 a flag is set once and never reset again. This flag is also sent to the driver to indicate that the destination has been reached and the car should not move again even though the distance is changing. | ||
− | |||
− | |||
<HR> | <HR> | ||
<BR/> | <BR/> | ||
+ | |||
== Mobile Application == | == Mobile Application == | ||
<Picture and link to Gitlab> | <Picture and link to Gitlab> | ||
Line 411: | Line 1,277: | ||
=== Software Design === | === Software Design === | ||
− | + | [[File:App1.jpg|500px|center|thumb|App Layout]][[File:Appcode.jpg|500px|center|thumb|App Code]] | |
=== Technical Challenges === | === Technical Challenges === | ||
Line 423: | Line 1,289: | ||
<BR/> | <BR/> | ||
== Conclusion == | == Conclusion == | ||
− | |||
− | + | TerraByte represented a monumental collaborative effort condensed into a brisk semester. The vast array of new skills and knowledge we acquired in such a short time was both daunting and exhilarating. This project not only highlighted our strengths and weaknesses but also provided us with extensive hands-on experience with industry-standard tools and techniques. Each team member gained valuable insights into CAN systems, motors, GPS, compasses, ultrasonic sensors, as well as practical skills in wiring and soldering. The project was a deep dive into both technical engineering and collaboration. | |
+ | |||
+ | ==== What we did you learned ==== | ||
+ | |||
+ | * Although the project seemed like a daunting task at the start. It became easy overtime by splitting it into each node people can work on can collaborate | ||
+ | * A whole lot about CAN communication and industry tools like BUSMASTER. Anytime something went wrong with CAN bus we were able to diagnose the problems easily with whatever we learned in class as well as using BUSMASTER | ||
+ | * Not every demo will go according to plan. don't get hung up and lose your head. trusting our abilities, being confident, and keeping on working - that was our mantra | ||
=== Project Video === | === Project Video === | ||
+ | |||
+ | video [https://drive.google.com/file/d/1p_igjR9eC7sTB5iHP1i98-rehn9LzG-E/view?usp=sharing] | ||
=== Project Source Code === | === Project Source Code === | ||
− | === | + | Link [https://gitlab.com/minalupadhye/C243_terrabyte] |
− | + | ||
+ | === Advice for Future Students === | ||
+ | |||
+ | Invest time in PCB, our final demo didn’t go well do to a loose connection in wiring. | ||
+ | |||
+ | ===== Geo node ===== | ||
+ | |||
+ | |||
+ | *Buy the CMPS-12 or CMPS 14 compass module and don’t waste your time calibrating a cheaper sensor. | ||
+ | |||
+ | *Do the GPS assignment in class very thoroughly. Get sample GPS strings and write unit test cases for them while developing the algorithm, so you don’t have to spend more time later on. | ||
+ | |||
+ | *The distance between points need not be found using the haversine formula, an easier triangle formula can also be used. Your car will not travel around the world to account for the curve! | ||
+ | |||
+ | *The math used in the geo node will lose some precision due to using float datatype. This causes slight variations in the coordinate parsing. While doing the checkpoint algorithm: go to the 10th Street garage, stand at each checkpoint, and fetch the current location from the GPS module instead of getting it from google maps. | ||
+ | |||
+ | ===== Motor node ===== | ||
+ | # Visualizing the output/ response on oscilloscope is very helpful. | ||
+ | # Learn to perform calibration using CAN. P Controller requires a lot of calibration fine tuning. | ||
+ | # PI controller is always better than just the P controller for more stable performance. | ||
=== Acknowledgement === | === Acknowledgement === | ||
+ | Grateful to Preet without whom this project wouldn't have been possible. @Preet thank you for all the knowledge you shared with us. | ||
+ | <br> We are also very grateful to TAs Kyle and Ninaad for their support. | ||
+ | |||
+ | ===== Geo references ===== | ||
+ | * Distance and bearing [https://www.movable-type.co.uk/scripts/latlong.html] | ||
+ | * Compass Datasheet [https://www.robot-electronics.co.uk/files/cmps12.pdf] | ||
+ | * GPS datasheet and Wiki [https://www.adafruit.com/product/746] | ||
+ | |||
+ | ===== Motor references ===== | ||
+ | * Understanding Motor Powertrain [https://www.youtube.com/watch?v=TZ_I9gQHgv4&t=1s] | ||
=== References === | === References === |
Latest revision as of 01:44, 26 May 2024
Contents
TerraByte
Abstract
The objective of this project was to construct an autonomous vehicle capable of navigating to a specified destination while avoiding obstacles along its path. The destination is selected through an Android application, which communicates the destination coordinates to the vehicle via Bluetooth. The Sensor and Bridge module receives these coordinates and transmits them to the Geo module via CAN. Subsequently, the Geo module utilizes GPS and Compass data to guide the vehicle to the designated destination. The Driver module receives inputs from the ultrasonic sensors of the Sensor and Bridge module and directives from the Geo module, then sends appropriate commands to the motor module. Finally, the Motor module propels the vehicle according to the instructions provided by the Driver module. The primary focus of this project lies in the CAN communication among the four modules: Sensor and Bridge module, Geo module, Driver module, and Motor module.
Introduction
The project is divided into 5 modules:
- Sensor Node
- Motor Node
- Driver Node
- Geo Controller Node
- Android App
Team Members & Responsibilities
Gitlab Project Link - C243_TerraByte
Module | Owner | Supporter |
---|---|---|
Sensor + Bridge | Vamsi Rushi Dhanekula | - |
Driver + LCD | Minal Upadhye | Susmitha |
Motor + ESC | Susmitha Ganesh | Minal |
GPS + Geo Controller | Harikrishnan Kokkanthara Jeevan | - |
App | Dikshant Kotla | Vamsi |
- Sensor & Bridge
- Motor
- Geographical
- Driver & LCD
- Android Application
- Link to Gitlab user1
Schedule
Week# | Start Date | End Date | Task | Status |
---|---|---|---|---|
1 | 03/04/2024 | 03/10/2024 |
|
Completed |
2 | 03/04/2024 | 03/10/2024 |
|
Completed |
3 | 03/11/2024 | 03/17/2024 |
|
Completed |
4 | 03/18/2024 | 03/24/2024 |
|
Completed |
5 | 03/25/2024 | 03/31/2024 |
|
Completed |
6 | 04/01/2024 | 04/07/2024 |
PROTOTYPE 1
|
Completed |
7 | 04/08/2024 | 04/14/2024 |
|
Completed |
8 | 04/15/2024 | 04/21/2024 |
PROTOTYPE 2
|
Completed |
9 | 04/22/2024 | 04/28/2024 |
|
Completed |
10 | 04/29/2024 | 05/05/2024 |
PROTOTYPE 3
|
Completed |
11 | 05/06/2024 | 05/12/2024 |
PROTOTYPE 4
|
Completed |
12 | 05/13/2024 | 05/19/2024 |
|
Completed |
Parts List & Cost
Item# | Part Desciption | Vendor | Qty | Cost |
---|---|---|---|---|
1 | RC Car - Traxxas Slash 2Wd BL | Traxxas [1] | 1 | $260.00 |
2 | SJ2 boards | CMPE SCE | 4 | $50.00 each |
3 | CAN Transceivers | Adafruit [2] | 4 | $3.95 each |
4 | LCD module 20*4 | GeekPi [3] | 1 | $10.99 |
5 | Bluetooth module | Adafruit [4] | 1 | $17.50 |
6 | Ultrasonic Sensors | Adafruit [5] | 4 | $6.95 each |
7 | GPS module | Adafruit [6] | 1 | $29.95 |
8 | Compass | Adafruit [7] | 1 | $29.90 |
9 | GPS Antenna | Antenna[8] | 1 | $8.90 |
10 | RPM Sensor | Reed switch [9] | 1 | $7.90 |
11 | DB9 connector female | Amazon [10] | 1 | $9.99 |
12 | Jumper wires | Amazon [11] | 2 | $6.98 each |
13 | Prototyping Board | Amazon [12] | 2 | $7.90 |
Printed Circuit Board
To handle the sheer complexity of connections, a custom PCB was considered as early as the prototype 1. Designed in KiCAD, from schematic capturing to PCB layout.
Challenges
We found ourselves going back and forth with soldering and adjusting connections, therefore we opted for a more robust PCB based design.
Design Steps
- Identify desired mounting topology and make measurements
- Use a CAD software to capture schematic
- Define a PCB size and place mounting holes
- Plan PCB layout and start routing
- Check for ERC and DRC errors
- Get design files ready for manufacturing
CAN Communication
Hardware Design
DBC File
DBC file [13]
VERSION "Final"
NS_ :
BA_
BA_DEF_
BA_DEF_DEF_
BA_DEF_DEF_REL_
BA_DEF_REL_
BA_DEF_SGTYPE_
BA_REL_
BA_SGTYPE_
BO_TX_BU_
BU_BO_REL_
BU_EV_REL_
BU_SG_REL_
CAT_
CAT_DEF_
CM_
ENVVAR_DATA_
EV_DATA_
FILTER
NS_DESC_
SGTYPE_
SGTYPE_VAL_
SG_MUL_VAL_
SIGTYPE_VALTYPE_
SIG_GROUP_
SIG_TYPE_REF_
SIG_VALTYPE_
VAL_
VAL_TABLE_
BS_:
BU_: DBG DRIVER MOTOR SENSOR_BRIDGE GEO
BO_ 50 APP_COMMAND: 1 SENSOR_BRIDGE
SG_ BRIDGE_START_STOP : 0|1@1+ (1,0) [0|0] "Bool" DRIVER
BO_ 99 DRIVER_HEARTBEAT: 1 DRIVER
SG_ DRIVER_HEARTBEAT_cmd : 0|8@1+ (1,0) [0|0] "" SENSOR_BRIDGE,MOTOR
BO_ 100 SENSOR_DATA: 4 SENSOR_BRIDGE
SG_ SENSOR_DATA_left : 0|8@1+ (1,0) [0|0] "cm" DRIVER
SG_ SENSOR_DATA_right : 8|8@1+ (1,0) [0|0] "cm" DRIVER
SG_ SENSOR_DATA_front : 16|8@1+ (1,0) [0|0] "cm" DRIVER
SG_ SENSOR_DATA_rear : 24|8@1+ (1,0) [0|0] "cm" DRIVER
BO_ 200 GEO_STATUS: 8 GEO
SG_ GEO_STATUS_COMPASS_HEADING : 0|12@1+ (1,0) [0|359] "Degrees" DRIVER, SENSOR_BRIDGE
SG_ GEO_STATUS_COMPASS_BEARING : 12|12@1+ (1,0) [0|359] "Degrees" DRIVER, SENSOR_BRIDGE
SG_ GEO_STATUS_DISTANCE_TO_DESTINATION : 24|32@1+ (0.1,0) [0|0] "Meters" DRIVER, SENSOR_BRIDGE
SG_ GEO_STATUS_VALID_FLAG: 56|1@1+ (1,0) [0|1] "Bool" DRIVER, SENSOR_BRIDGE
SG_ GEO_STATUS_DESTINATION_REACHED: 57|1@1+ (1,0) [0|1] "Bool" DRIVER, SENSOR_BRIDGE
BO_ 250 GPS_CURRENT_LOCATION: 8 GEO
SG_ GPS_CURR_LATITUDE_SCALED_1000000 : 0|32@1- (1,0) [-90000000|90000000] "Degrees" SENSOR_BRIDGE
SG_ GPS_CURR_LONGITUDE_SCALED_1000000 : 32|32@1- (1,0) [-180000000|180000000] "Degrees" SENSOR_BRIDGE
BO_ 300 DRIVER_CONTROL: 2 DRIVER
SG_ DRIVER_CONTROL_steer : 0|3@1- (1,0) [-2|2] "" MOTOR
SG_ DRIVER_CONTROL_speed : 3|4@1- (1,0) [-1|2] "kph" MOTOR
BO_ 400 MOTOR_STATUS: 2 MOTOR
SG_ MOTOR_STATUS_wheel_speed : 0|12@1+ (0.1,-70) [-70|70] "kph" SENSOR_BRIDGE
BO_ 500 GPS_DESTINATION_LOCATION: 8 SENSOR_BRIDGE
SG_ GPS_DEST_LATITUDE_SCALED_1000000 : 0|32@1- (1,0) [-90000000|90000000] "Degrees" GEO
SG_ GPS_DEST_LONGITUDE_SCALED_1000000 : 32|32@1- (1,0) [-180000000|180000000] "Degrees" GEO
BO_ 600 DEBUG_DRIVER: 2 DRIVER
SG_ DRIVER_OA_or_W : 0|1@1+ (1,0) [0|0] "Bool" DBG
SG_ DRIVER_OA_state : 1|8@1+ (1,0) [0|0] "Char" DBG
BO_ 602 DEBUG_MOTOR: 3 MOTOR
SG_ MOTOR_PCTRLR_PWM_debug : 0|7@1+ (0.1,0) [10|20] "%" DBG
SG_ MOTOR_ROTATIONSPER2S_debug : 7|12@1+ (1,0) [0|0] "" DBG
BO_ 603 DEBUG_GEO: 1 GEO
SG_ GEO_Compass_heartbeat : 0|1@1+ (1,0) [0|0] "Bool" DBG
CM_ BU_ DRIVER "The driver controller driving the car";
CM_ BU_ MOTOR "The motor controller of the car";
CM_ BU_ SENSOR "The sensor controller of the car";
CM_ BO_ 100 "Sync message used to synchronize the controllers";
CM_ SG_ 100 DRIVER_HEARTBEAT_cmd "Heartbeat command from the driver";
BA_DEF_ "BusType" STRING ;
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 0;
BA_DEF_ SG_ "FieldType" STRING ;
BA_DEF_DEF_ "BusType" "CAN";
BA_DEF_DEF_ "FieldType" "";
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_ "GenMsgCycleTime" BO_ 100 1000;
BA_ "GenMsgCycleTime" BO_ 200 50;
BA_ "FieldType" SG_ 100 DRIVER_HEARTBEAT_cmd "DRIVER_HEARTBEAT_cmd";
VAL_ 100 DRIVER_HEARTBEAT_cmd 2 "DRIVER_HEARTBEAT_cmd_REBOOT" 1 "DRIVER_HEARTBEAT_cmd_SYNC" 0 "DRIVER_HEARTBEAT_cmd_NOOP" ;
Sensor Controller & Bridge
There are 4 sensors fitted on the car: Front, Rear, Left and Right.
Hardware Design
Software Design
Technical Challenges
Driver Controller & LCD
The LCD is a GeekPi 20x4 display using I2C. LCD displays some critical information during runtime which is critical for debugging. This information is split into 4 lines.
- Line1 displays sensor data from 4 sensors in the sequence Back(Rear), Left, Front, Right.
- Line2 displays command to motor from driver which are two signals: the speed (forward, stop, reverse) and steer values (left, right, straight).
- Lines 3 and 4 display information related to Geo module. Line 3 displays distance to destination and whether the current coordinates are valid (valid flag).
- Line4 displays current heading and bearing values from compass.
Hardware Design
The SJ2 board hosting the driver module is connected to -
- CAN Transceiver
SJ2 is connected to CAN bus through the CAN transceiver as shown in the diagram.
- LCD
LCD is powered by 5V input from the power bank and a common ground with SJ2. LCD is connected to SJ2 using I2C, hence the SDA and SCL pins that transfer data to be printed.
- Sensor LEDs
There is one LED per sensor i.e. Front, Rear, Left and Right which are connected to GPIO pins on SJ2.
Software Design
Driver module has 2 main objectives
- Process the input from sensor and geo module and provide control output to motor module.
- Display important information on the LCD.
Driver_logic() is the core part which manages the entire module. API obstacle_avoidance() takes sensor values as input and decides the direction and speed to move. Similarly, API waypoint_algo() takes geo input and decides the direction and speed to move.
Driver module decides which one to follow in every iteration of the periodic function it is scheduled. While it is important to move towards destination and follow geo inputs, obstacle avoidance has priority over it.
Driver Logic
if (obstacle_avoidance_flag) { motor_control_to_transmit = obstacle_avoidance(); } else { motor_control_to_transmit = waypoint_algo(received_geo_status); }
Command to motor (Steer)
typedef enum { HARD_LEFT = -2, SOFT_LEFT, NO_STEER, SOFT_RIGHT, HARD_RIGHT, } steer__val_e;
Obstacle Avoidance is basically a state machine with three states - Forward, Stop and Reverse. The default state is Stop and then based on sensor inputs, the state transitions are made.
Obstacle Avoidance states
static void stop_state(void) { sensor_based_motor_command = move_stop(); if (front_sensor_cm < FRONT_STOP_THRESHOLD) { OA_state = 'R'; } else { OA_state = 'F'; } } static void reverse_state(void) { if (front_sensor_cm < FRONT_STOP_THRESHOLD) { sensor_based_motor_command = move_reverse(); } else { OA_state = 'S'; } } static void forward_state(void) { if (front_sensor_cm < FRONT_STOP_THRESHOLD) { OA_state = 'S'; } else if ((left_sensor_cm < SIDE_SOFT_THRESHOLD) || right_sensor_cm < SIDE_SOFT_THRESHOLD) { if (left_sensor_cm < right_sensor_cm) { process_left_sensor(); } else { process_right_sensor(); } } else { sensor_based_motor_command = move_forward_slow(); } }
Waypoint algorithm takes Compass heading and bearing as input and decides the direction to move. This is achieved by calculating the angle required to turn and further the optimized angle.
Waypoint angle calculation
static int16_t calculate_angle(uint16_t heading, uint16_t bearing) { int16_t angle; angle = bearing - heading; if (angle > 180) { angle = angle - 360; } else if (angle < -180) { angle = angle + 360; } return angle; }
There are a few more conditions around which the driver module operates:
- Initially, the car will only start moving if receives a START indication from the app.
- The car will stop whenever a STOP is indicated from the app.
- The car will stop if the destination is reached.
App Commands
if (app_cmd_received.BRIDGE_START_STOP == true) { driver__process_input(); driver__process_geo_controller_directions(); } else { motor_control_to_transmit = move_stop(); } }
Apart from this, the sensor LEDs are also controlled by driver module. The sensor LEDs glow if an obstacle is detected by a particular sensor based on the specified threshold.
Sensor LEDs
if (front_sensor_cm < FRONT_STOP_THRESHOLD) { set_led_front(); } else { reset_led_front(); } if (rear_sensor_cm < REAR_THRESHOLD) { set_led_rear(); } else { reset_led_rear(); } if (left_sensor_cm < SIDE_SOFT_THRESHOLD) { set_led_left(); } else { reset_led_left(); } if (right_sensor_cm < SIDE_SOFT_THRESHOLD) { set_led_right(); } else { reset_led_right(); }
Technical Challenges
- LCD delays caused SJ2 microcontroller reset.
LCD requires delays to work properly. Calling the LCD_API in periodics reset the SJ2 microcontroller. To solve this problem, a separate task (not in periodics) in main() was created with a separate stack which resolved the problem.
Motor Controller
Hardware Design
The motor controller, is the SJ2 board linked to powertrain, wheel encoder and CAN transceiver, which manages the motor's operation. It follows the direction and speed instructions provided by the Driver module. Additionally, it measures the wheel speed and maintains the preset speed across different terrains. This wheel speed data is transmitted to the Sensor-Bridge module for display on the Android App.
Powertrain
We used Traaxas Slash XL5 RWD for this project. The ESC (XL-5), BLDC motor, servo motor and battery come with the RC car. The 6V 3000mAh NiMH battery powers the ESC (XL-5), servo and BLDC motor. The ESC controls the BLDC however the servo is not connected via the ESC. The BLDC controls the forward, reverse and brake motion of the car along with the respective speed in the forward and reverse direction. The servo controls the steer angle. Brake and speed in both forward and reverse in case of BLDC motor and steer angle in case of Servo motor are controlled by PWM signals.
The ESC operates in 3 different modes as per the user manual, we choose to operate the car in Training mode where the forward and reverse speeds are half its original speeds. The maximum speed the car could reach is around 30Kmph in this mode. This mode can be set by long pressing the button on the ESC and releasing the button when there are 3 consecutive red blinks.
For the RC car the PWM signals are provided by the Radio Transmitter + Remote. We tried to understand the sequence of PWM that is to be provided to keep the ESC calibrated, for forward, reverse and brake by providing the commands on the remote and checking the waveform on oscilloscope.
The following were our observations which helped us develop motor control module:
STATE | PWM | COMMENTS |
---|---|---|
ESC Calibration | 15% |
|
Forward | 15.1% to 20% | Speed increases with the PWM dutycycle |
Brake |
| |
Reverse | 10% to 14.9% | Speed increases with the decrease in PWM dutycycle |
Right | 15.1% to 20% | Angle increases with the PWM dutycycle |
Left | 10% to 14.9% | Angle increases with decrease in PWM dutycycle |
No steer | 15% |
Wheel Encoder
We opted reed switch for wheel encoding.
Placement : The magnet, along with its holder, was positioned on the spur gear, while the reed switch was housed within the casing covering the spur gear. The reed switch is connected between GPIO pin and ground. (NOTE: All GPIO pins of SJ2 board are pulled up to 3.3 V).
The reed switch is usually open (giving a 3.3V when observed on oscilloscope) but when it crosses the magnet the reed switch closes giving a 0V. The number of edge falls in a minute gives the rpm. This is converted into linear speed by considering the circumference of wheel and gear to wheel ratio (since the wheel encoder was placed in the spur gear every 2.73 rotations of spur gear yields 1 rotation of wheel).
Software Design
Motor Controller has the following code modules in periodics:
- Initialization
- CAN Initialization
- Configure GPIO pins P0.0 and P0.1 for CAN Rx and Tx respectively.
- Set the baud rate for CAN communication.
- Configure the CAN Tx and Rx buffer sizes.
- Enable CAN reset in case of bus off.
- Bypass the CAN software filter.
- Set PWM on P2.0 as 15% and set a delay for 3s for ESC calibration
- PWM Initialization
- Configure GPIO pins P2.0 and P2.1 for PWM functionality.
- Set the PWM frequency to 100 Hz.
- Set the PWM dutycycle on P2.0 as 15% and a delay of 3s for ESC calibration
- Interrupt Initialization
- Configure GPIO pin P0.6 as an input.
- Enable interrupts on both rising and falling edges for this pin.
- Define the interrupt service routine.
- CAN Initialization
- 1Hz Tasks
- Calculate Wheel Speed
- 20Hz Tasks
- Incoming CAN Message Handling:Process incoming Speed Level and Steer Angle messages from the Driver module. Handle incoming CAN messages appropriately along with Mia Handling.
- Motor Controller: Execute the main function of the motor controller.
- CAN Transmission: Transmit the wheel speed over CAN.
Calculate Wheel Speed
The number of falling edges recorded on P0.6 in 1 minute yields rpm of the spur gear. The function to calculate wheel speed translates the rpm of spur gear into wheel speed. (Note: This function runs every 1s though the naming indicates 0.5Hz). Once the speed is calculated the number of rotations of the spur gear that is accumulated is cleared.
void calcuate_speed_0_5Hz(void) { const float wheel_circumference = 34.54; // in cms const float gear_ratio = 2.73; speed = 3600 * rotations_cnt * wheel_circumference / (1000 * 100 * gear_ratio); rotations_cnt = 0; }
Main Motor Controller Function
Flowchart of the Main Motor Controller functionality is depicted below. The speed level and steer angle commands are received from Driver module via CAN as indicated below.
Forward Reverse or Brake Helper Function
Flowchart for the helper function to decide if the car has to move forward, reverse or brake is shown below.
In the forward direction, a P controller is activated to uphold the preset speed regardless of the terrain. The speed inputted by the driver module is in speed levels, which are then translated into target speeds in kilometers per hour (km/h) for the P controller's operation. However, in reverse and during braking, the PWM sequence remains constant.
Speed Level Converter : Speed Level from Driver Module converted to Target speed in Km/h
float speedlevel_to_targetspeed(int8_t speed_level) { float target_speed; switch (speed_level) { case 1: target_speed = 3; // in kmph break; case 2: target_speed = 5; // in kmph break; default: target_speed = 3; // in kmph break; } return target_speed; }
P Controller :
The P controller is set different for positive and negative errors, this was done so that the speed is quickly decreased after climbing a ramp. However this is not required for flat surfaces. The maximum pwm output from P controller is se3t to 16.8% dutycycle and minimum PWM is set to 16% dutycycle.
float p_Ctrlr(float dutycycle, float target_speed, float current_speed) { float err; float Kp = 0; const float Kp_f = 0.01; const float Kp_r = 0.1; const float pwm_min_forward = 16; const float pwm_max_forward = 16.8; err = target_speed - current_speed; if (err > 0.5) { Kp = Kp_f; } else if (err < -0.5) { Kp = Kp_r; } else { Kp = 0; } dutycycle = min(max(((err * Kp) + dutycycle), pwm_min_forward), pwm_max_forward); return dutycycle; }
Steer Helper Function
Below is the flowchart depicting the Steer Helper function. Depending on the driver's steer angle command, the car is capable of executing sharp or gentle turns to the left or right, or maintaining a straight trajectory with no steering input.
Technical Challenges
1. Issue: The car occasionally engaged in reverse and failed to do so at other times.
Cause: To streamline the code, the brake state was only activated during the transition from forward to reverse; otherwise, a PWM <15% was directly applied.
Solution: Always adhere to the reverse sequence: Brake-Neutral-Reverse.
2. Issue: After implementing the P controller, the car moved in reverse (at high speed) instead of forward.
Cause:
- The calculated speed was excessively high due to noise in the reed switch during transition from OFF to ON, resulting in more edge falls than expected, leading to an overly high calculated wheel speed.
- The calculated speed was excessively high due to noise in the reed switch during transition from OFF to ON, resulting in more edge falls than expected, leading to an overly high calculated wheel speed.
- Placing the magnet on the spur gear caused the reed switch to count the rotations of the spur gear, necessitating consideration of the spur gear-to-wheel rotation ratio.
- Placing the magnet on the spur gear caused the reed switch to count the rotations of the spur gear, necessitating consideration of the spur gear-to-wheel rotation ratio.
Solution:
- To mitigate noise, edge fall counting was enabled only if the subsequent edge rise occurred beyond 700 microseconds.
- The gear-to-wheel ratio was taken into account.
- Additionally, maximum and minimum limits of 16.8 and 16, respectively, were imposed on the P controller output to prevent the RC car from moving excessively fast or too slowly.
Geographical Controller
The geo controller has a dedicated SJ2 board attached to the CAN bus via a transceiver. The main task of the geo controller is to provide the driver node with directions to the destination location.
The geo node is attached with GPS and compass modules for this task.
Hardware Design
The following image shows the hardware connections of the GEO node
GPS Module
The GPS module used is an Adafruit Breakout V3, PA1616S
- It is a UART-based module that runs at 9600bps by default and can be configured up to 115200bps.
- It is attached to the SJ2 board on UART3 on pins P4.28(TX3) and P4.29(RX3).
- The TX is connected to the RX of the GPS and the RX pin is connected to the TX of the GPS.
- The GPS module outputs various NMEA strings like $GPGLL, $GPGSA etc… out of which we are concerned with only 1 type of string which is the $GPGGA - Global Positioning Systems Fix Data.
- Once the GPS gets a fix, the fix LED on the module will start blinking every 15 seconds.
- a button cell CR1220 is added to the GPS module to hotstart the module and get a fix faster
The $GPGGA string is of the format:
- $GPGGA,HHMMSS.SS,DDMM.MMMMM,K,DDDMM.MMMMM,L,N,QQ,PP.P,AAAA.AA,M,±XX.XX,M, SSS,RRRR*CC<CR><LF>
Name | Example | Description |
---|---|---|
Message ID | $GPGGA | GGA protocol header |
UTC Time | 002153.000 | hhmmss.sss |
Latitude | 3342.6618 | dddmm.mmmm |
N/S indicator | N | North or South Indicator |
Longitude | 11751.3858 | dddmm.mmmm |
E/W Indicator | W | East or West Indicator |
Position Fix Indicator | 1 | Indicates if there is a satellite fix |
Satellite Used | 10 | Range 0 to 12 |
HDOP | 1.2 | Horizontal Dilution of Precision |
MSL Altitude | 27.0 | |
Units | M | |
Geoid Separation | -34.2 | Geoid-to-ellipsoid separation. Ellipsoid altitude = MSL Altitude + Geoid Separation |
Units | M | |
Age of Diff. Corr. | Null fields when DGPS is not used | |
Diff. Ref. Station ID | 000 | |
Checksum | *5E | |
<CR><LF> | End of Message termination |
- We are interested in the Message ID, latitude, N/S indicator, Longitude, E/W Indicator, and Position Fix Indicator, which the parsing algorithm will parse from the line buffer.
- The output will be the current location in terms of latitude and longitude of the format (3342.6618, 11751.3858).
Compass
Important Note to future students: Under no circumstances buy a compass that you have to manually calibrate like the LSM303AGR or LSM303DLHC (see technical challenges for more information)
- The compass module used is a CMPS12 from Bosch. It was chosen because it works out of the box, is tilt-calibrated, and does not require any additional calibration to account for the magnetic declination, soft iron, and hard iron offsets.
- The compass is interfaced to the SJ2 board using the I2C_2 pins P0.10 (SDA) and P0.11 (SCL) respectively.
Software Design
The Geo node software was split into the following modules to efficiently handle the code:
- Can
- Handles the can initialization, encode & send and the MIA configuration
- Checkpoint
- The checkpoint algorithm to help the car reach the destination at a particular location
- Geo Message Bank
- Handles all the sending and receiving of the CAN messages to and from the Geo node in a single place
- Geo Status
- The main function used to gather all the geo node details and pack them into a CAN message
- GPS
- The GPS parsing Algorithm
- LED handle
- Used to handle the SJ2 Board's LEDs for debugging and indication purposes while the car is running
- Line buffer
- Support Module for the GPS parser
Initialization Phase
- The GPS module can and should be configured to only output GPGGA strings during the initialization phase of the scheduler. This makes it easier to have a smaller line buffer and less overhead
- The command used: "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n"
- Apart from this, the GPS module along with the SJ2 UART should be raised to 115200bps to get the best possible speed when running the GPS_run_once() function at 20hz. However, we found that raising the speed to 115200 was not working. See the technical challenges section for more details.
- Very little configuration during the initialization phase is required to start getting the heading values from the compass in fact, if you are using I2C_2 by default I2C is initialized with 400khz and you don’t need to do any setup.
Main Loop
- The geo controller's main task is to send a status message ‘GEO_STATUS’ to the driver node. This frame contains details like the heading, bearing, distance to destination, and some flags to help the car reach the destination.
- All function calls CAN receive, CAN send and gps_run_once are called in 20hz frequency.
- gps_run_once() function call fetches the latest NMEA GPGGA string from the line buffer and parses it to extract the latitude and longitude along with GPS fix data. On successful parsing of the string, a debug flag is also set. If the parsing was successful and a fix was present an LED on the SJ2 board was lit up.
- The destination to which the car has to reach set from the Android app is sent by the SENSOR_BRIDGE module via CAN.
- Once the destination co-ordinate has been received the bearing and distance to the destination are computed.
- To compute the bearing we use both coordinates the current location received from the GPS module and the destination location received from the SENSOR_BRIDGE node. The values are fed into the below formula and converted into degrees from radians.
- To compute the distance to the destination the haversine formula is used and output is formatted into meters for better accuracy. If the distance is less than 3m a flag is set once to indicate that the final destination is reached. More on this in the technical challenges section.
- To compute the heading the geo_status() function simply fetches the latest values from the compass and applies the constants described in the datasheet.
- Once all this data is computed the ‘GEO_STAUS’ message is populated and then sent to the driver node in the CAN transmission function call.
- A message bank module was adopted where all messages to be sent and received from the CAN are processed.
checkpoint Algorithm
The checkpoint algorithm is used to make the navigation of the car easier in the final demo location. it follows the closest checkpoints from source to destination in an effective manner
- 37.339725 ,-121.881119
- 37.339764 ,-121.881073
- 37.339581 ,-121.880928
- 37.339539 ,-121.881035
- 37.339375 ,-121.880890
- 37.339436 ,-121.880760
- 37.339291 ,-121.880646
- 37.339226 ,-121.880745
- 37.339088 ,-121.880630
- 37.339134 ,-121.880516
- 37.338947 ,-121.880371
- 37.338882 ,-121.880486
- The checkpoints shown above were used as inputs to the Algorithm along with the current location and the destination location sent by the bridge node
- The function effectively evaluates each predefined checkpoint against the final destination to determine the closest, valid next step for navigation, optimizing for the nearest reachable point that progresses towards the final destination. It does this efficiently by precomputing distances to avoid redundant calculations during iteration.
Appendix GEO
Parsing Algorithm
static bool gps__parse_coordinates_from_line(void) {
char gps_line[200];
bool return_value = false;
uint8_t field_count = 0;
if (line_buffer__remove_line(&line, gps_line, sizeof(gps_line))) {
// set co-ordinate as invalid initially and gps-fix to false
parsed_coordinates.valid = false;
gps_fix = false;
// copy to reparse
char gps_line_copy[200];
strncpy(gps_line_copy, gps_line, sizeof(gps_line_copy));
// parse and get the preamble
char *token = strtok(gps_line_copy, ",");
field_count++;
// Check if the line is a GPGGA sentence
if (token && strcmp(token, "$GPGGA") == 0) {
// Skip the time field which is the second token
char *time = strtok(NULL, ",");
field_count++;
// skip other fields to first check fix
for (int i = 0; i < 5; ++i) {
token = strtok(NULL, ",");
field_count++;
if (token == NULL) {
return_value = false;
break;
}
}
if (token && field_count == 7) {
int fix_quality = atoi(token);
if (fix_quality > 0) {
gps_fix = true;
turn_on_gps_fix_led();
// GPS fix, parse latitiude and longititude
// Re-parse the original line for latitude and longitude
strtok(gps_line, ","); // skip $GPGGA
strtok(NULL, ","); // skip Time
char *latitude = strtok(NULL, ",");
char *latitude_direction = strtok(NULL, ",");
char *longitude = strtok(NULL, ",");
char *longitude_direction = strtok(NULL, ",");
if (latitude && longitude && latitude_direction && longitude_direction) {
// Convert and adjust coordinates
float parsed_latitude = convert_from_minutes_to_decimal(atof(latitude));
float parsed_longitude = convert_from_minutes_to_decimal(atof(longitude));
if (*latitude_direction == 'S') {
parsed_latitude *= -1.0f;
}
if (*longitude_direction == 'W') {
parsed_longitude *= -1.0f;
}
parsed_coordinates.latitude = parsed_latitude;
parsed_coordinates.longitude = parsed_longitude;
parsed_coordinates.valid = true;
return_value = true;
} else {
return_value = false;
}
} else {
turn_off_gps_fix_led();
gps_fix = false;
return_value = false;
}
}
} else {
return_value = false;
}
} else {
return_value = false;
}
return return_value;
}
Periodic scheduler callbacks 20hz
void periodic_callbacks__100Hz(uint32_t callback_count) {
if (callback_count % 5 == 0) {
msg_bank__handle_msg_rx();
msg_bank_geo__service_mia_10hz();
gps__run_once();
msg_bank__handle_msg_tx();
}
}
Geo_status function
void geo_get_status_2(dbc_GEO_STATUS_s *msg) {
float distance = 0.0f;
uint16_t bearing = 0;
uint16_t heading = 0;
float haversine_angle = 0.0f;
gps_coordinates_t gps_current_reading = gps__get_coordinates();
bool geo_current_valid = gps_current_reading.valid;
bool geo_dest_valid = geo_get_destination_coordinates();
if (geo_current_valid) {
if (geo_dest_valid) {
// Always calculate distance to final destination for the flag logic
haversine_angle = helper_find_haversine_angle(gps_current_reading, gps_destination);
distance_to_final_destination = find_distance(haversine_angle);
bearing = find_bearing(gps_current_reading, gps_destination);
#ifdef CHECKPOINT_ALGORITHM
// If using checkpoints, calculate nearest checkpoint and get distance to it for navigation
gps_coordinates_t nearest_checkpoint =
find_nearest_checkpoint(gps_destination, gps_current_reading, distance_to_final_destination);
haversine_angle = helper_find_haversine_angle(gps_current_reading, nearest_checkpoint);
distance = find_distance(haversine_angle);
bearing = find_bearing(gps_current_reading, nearest_checkpoint);
#else
// Not using checkpoints, use direct distance and bearing already calculated
distance = distance_to_final_destination;
#endif
} else {
// Use the last known good destination if new coordinates are invalid
haversine_angle = helper_find_haversine_angle(gps_current_reading, gps_destination);
distance_to_final_destination = find_distance(haversine_angle);
distance = distance_to_final_destination;
bearing = find_bearing(gps_current_reading, gps_destination);
}
// Check if the final destination is reached
if (distance_to_final_destination <= 1.5f) {
distace_zero_true = true;
turn_on_SJ2_LED_for_final_destination_reached();
}
}
heading = compass2__get_heading();
// Set message signals
msg->GEO_STATUS_DISTANCE_TO_DESTINATION = distance_to_final_destination;
msg->GEO_STATUS_COMPASS_HEADING = heading;
msg->GEO_STATUS_COMPASS_BEARING = bearing;
msg->GEO_STATUS_VALID_FLAG = geo_current_valid | geo_dest_valid;
msg->GEO_STATUS_DESTINATION_REACHED = distace_zero_true;
}
Compass Data reading function
bool static compass2__read_once_magnetometer_data(void) {
const uint8_t COMPASS_HEADING_BYTES = 2;
uint8_t received_raw_data[2] = {0};
compass_heartbeat = false;
compass_heartbeat = i2c__read_slave_data(i2c_bus_port_compass, CMPS12_ADDRESS, CMPS12_BOSCH_ANGLE_BNO055,
received_raw_data, COMPASS_HEADING_BYTES);
if (compass_heartbeat) {
compass_heading_degrees = ((float)(((uint16_t)received_raw_data[0] << 8) | ((uint16_t)received_raw_data[1]))) /
CMPS12_BOSCH_SCALING_FACTOR;
}
return compass_heartbeat;
}
Checkpoint algorithm
gps_coordinates_t find_nearest_checkpoint(gps_coordinates_t final_destination, gps_coordinates_t current_location,
float distance_to_final_destination) {
float min_distance_to_current = FLT_MAX;
gps_coordinates_t nearest_checkpoint = {0};
bool found_valid_checkpoint = false;
float checkpoint_to_final_distances[NUM_CHECKPOINTS];
// pre-compute
for (size_t i = 0; i < NUM_CHECKPOINTS; i++) {
checkpoint_to_final_distances[i] =
find_distance(helper_find_haversine_angle(north_side_garage_checkpoints[i], final_destination));
}
// Iterate through the checkpoints to find the nearest one
for (size_t i = 0; i < NUM_CHECKPOINTS; i++) {
if (checkpoint_to_final_distances[i] < distance_to_final_destination) {
float distance_to_checkpoint =
find_distance(helper_find_haversine_angle(current_location, north_side_garage_checkpoints[i]));
// Check if this checkpoint is the nearest one to the current location
if (distance_to_checkpoint < min_distance_to_current) {
min_distance_to_current = distance_to_checkpoint;
nearest_checkpoint = north_side_garage_checkpoints[i];
found_valid_checkpoint = true;
}
}
}
// Return the final destination if no valid checkpoint is found or if the nearest checkpoint is further than the final
// destination
if (!found_valid_checkpoint || min_distance_to_current > distance_to_final_destination) {
return final_destination;
} else {
return nearest_checkpoint;
}
}
Technical Challenges
The GPS module is a critical part of the RC car and has to be unit-tested and integration-tested (both tests are important equally for this node)
- If you are reading this then go ahead and order the CMPS12 or CMPS14 compass they are costlier but work out of the box and the code is way smaller and easier to work with. We initially bought the LSM303AGR but it gave us so many issues even after spending days to calibrate it.
- Problem: The command to set the GPS module to output only GPGGA strings was not working correctly.
- Cause: The UART peripheral takes time to get initialized.
- Solution: Add a delay after the UART init call is made in the periodic initialization before sending the config command.
- Problem: Unable to switch to the higher baud rates
- Cause: Memory allocation with the line buffer
- Solution: keep the baud rate as 9600 itself. Although this works, ideally it should be higher for the 20hz function.
- Problem: The parsing function was not working properly
- Cause: Without a fix, there will be empty values in the NMEA string, the algorithm did not account for this.
- Solution: make sure to include the GPS fix parameter in the algorithm to check if the NMEA string is complete.
- Problem: Once the car reaches its destination and the distance to the destination becomes 0, after some time the distance will suddenly increase and the car will start moving again.
- Cause: This is the nature of the GPS module, it keeps updating the coordinate with less precision due to math with floating point.
- Solution: Once the destination becomes 0 a flag is set once and never reset again. This flag is also sent to the driver to indicate that the destination has been reached and the car should not move again even though the distance is changing.
Mobile Application
<Picture and link to Gitlab>
Hardware Design
Software Design
Technical Challenges
< List of problems and their detailed resolutions>
Conclusion
TerraByte represented a monumental collaborative effort condensed into a brisk semester. The vast array of new skills and knowledge we acquired in such a short time was both daunting and exhilarating. This project not only highlighted our strengths and weaknesses but also provided us with extensive hands-on experience with industry-standard tools and techniques. Each team member gained valuable insights into CAN systems, motors, GPS, compasses, ultrasonic sensors, as well as practical skills in wiring and soldering. The project was a deep dive into both technical engineering and collaboration.
What we did you learned
- Although the project seemed like a daunting task at the start. It became easy overtime by splitting it into each node people can work on can collaborate
- A whole lot about CAN communication and industry tools like BUSMASTER. Anytime something went wrong with CAN bus we were able to diagnose the problems easily with whatever we learned in class as well as using BUSMASTER
- Not every demo will go according to plan. don't get hung up and lose your head. trusting our abilities, being confident, and keeping on working - that was our mantra
Project Video
video [14]
Project Source Code
Link [15]
Advice for Future Students
Invest time in PCB, our final demo didn’t go well do to a loose connection in wiring.
Geo node
- Buy the CMPS-12 or CMPS 14 compass module and don’t waste your time calibrating a cheaper sensor.
- Do the GPS assignment in class very thoroughly. Get sample GPS strings and write unit test cases for them while developing the algorithm, so you don’t have to spend more time later on.
- The distance between points need not be found using the haversine formula, an easier triangle formula can also be used. Your car will not travel around the world to account for the curve!
- The math used in the geo node will lose some precision due to using float datatype. This causes slight variations in the coordinate parsing. While doing the checkpoint algorithm: go to the 10th Street garage, stand at each checkpoint, and fetch the current location from the GPS module instead of getting it from google maps.
Motor node
- Visualizing the output/ response on oscilloscope is very helpful.
- Learn to perform calibration using CAN. P Controller requires a lot of calibration fine tuning.
- PI controller is always better than just the P controller for more stable performance.
Acknowledgement
Grateful to Preet without whom this project wouldn't have been possible. @Preet thank you for all the knowledge you shared with us.
We are also very grateful to TAs Kyle and Ninaad for their support.
Geo references
Motor references
- Understanding Motor Powertrain [19]
=== References ===