diff --git a/Diagrams/Overall Diagrams.pdf b/Diagrams/Overall Diagrams.pdf index 2ab47ce..9bd4ebf 100644 Binary files a/Diagrams/Overall Diagrams.pdf and b/Diagrams/Overall Diagrams.pdf differ diff --git a/README.md b/README.md index 569f751..2f89a02 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ cp freeRTOS-car.uf2 /media/$USER/RPI-RP2 ## Motors Motor module consist of 4 components: -1. [motor_init](frtos/motor/motor_init.h): Initialises the pins and struct stated in [motor_config](frtos/config/motor_config.h) and [car_config](frtos/config/car_config.h), and the free rtos tasks related. Call `motor_init` followed by `motor_tasks_init` to start. -2. [motor_speed](frtos/motor/motor_speed.h): Monitors the speed of the motors by interrupt. Each **falling edge** of the wheel encoder will trigger the interrupt and the time elapsed since last trigger will be recorded and thus calculate the speed and distance travelled since boot. Includes functions to adjust the duty cycle of the motor. -3. [motor_pid](frtos/motor/motor_pid.h): PID function to match the **right** motor duty cycle to the **left** motor such that the car can move straight, with 50ms interval. -4. [motor_direction](frtos/motor/motor_direction.h): Sets the direction of the motor to control the car using bit masks. Includes functions that work with the magnetometer to turn the car to/by specific yaw. +1. [motor_init](frtos/motor/motor_init.h): Initialises the pins and struct stated in [motor_config](frtos/config/motor_config.h) and [car_config](frtos/config/car_config.h), and the free rtos tasks related. Call `motor_init` followed by `motor_tasks_init` to initilise the parameters and define the tasks. +2. [motor_speed](frtos/motor/motor_speed.h): Monitors the speed of the motors by interrupt. Each **falling edge** of the wheel encoder will trigger the interrupt and the time elapsed since last trigger will be recorded and thus calculate the speed and distance travelled since boot. Typically, each interrupt will record one slot length of the distance travelled, calculated and defined in [motor_config](frtos/config/motor_config.h). Includes functions to adjust the duty cycle of the motor for speed control. +3. [motor_pid](frtos/motor/motor_pid.h): PID function to match the one motor duty cycle to the other motor such that the car can move straight, with 50ms interval, by comparing the difference in distance travelled by each wheel. +4. [motor_direction](frtos/motor/motor_direction.h): Sets and changes the direction of the motor to control the car using bit masks. Includes functions that work with the magnetometer to turn the car to/by specific yaw. ## Ultrasonic Sensor 1. Ultrasonic sensor package is in `frtos/ultrasonic_sensor`, and its configuration is in `frtos/config/ultrasonic_sensor_config.h`. It contains the drivers and FreeRTOS tasks to output and read the ultrasonic sensor data. @@ -44,6 +44,9 @@ Motor module consist of 4 components: ## Line Sensor +The Line sensor package is in `frtos/line_sensor`, and its configuration is in `frtos/config/line_sensor_config.h`. It +contains the drivers and FreeRTOS tasks to read the line sensor data and update the car's obstruction struct. + ## Magnetometer The magnetometer used is the [LSM303DLHC](https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf) from STMicroelectronics. @@ -65,7 +68,38 @@ a bias calculation. This method was not used in the final version, because the r The final version of the magnetometer uses a **moving average filter** to smooth the data and a bias of 0 degrees. ## Barcode (Line) Sensor - The barcode sensor uses edge interrupts to trigger the barcode sensor when the sensor hits a black line, which then measures the width of the black line to white, and stores it in an array. The Code39 barcode library is used as a reference for the barcode decoding function. ## WiFi (Web Server) Module + +The WiFi (Web Server) Module within this project integrates Server-Side Includes (SSI) and Common Gateway Interface (CGI) handlers from the lwIP library, facilitating a web interface to display information and receive controls from a webpage. + +### Components Overview + +1. **[frontend.h](frtos/frontend/frontend.h):** This component initializes and manages the web server frontend. It establishes the initial connection to the WiFi network using environment variables such as WIFI_SSID and WIFI_PASSWORD. + +2. **[html_files](frtos/frontend/html_files):** This directory contains HTML files used for the web server display. These files are linked into lwIP to enable server-side processing and rendering. + +3. **[ssi.h](frtos/frontend/ssi.h):** Handles Server-Side Includes (SSI), which dynamically inserts content into web pages, allowing the display of real-time or changing information. + +4. **[cgi.h](frtos/frontend/cgi.h):** Manages Common Gateway Interface (CGI) handlers, enabling the reception of controls and user interactions from the web interface. + +5. **makefsdata Utility:** This tool generates `htmldata.c` by converting HTML files into hexadecimal representation to link them into lwIP. It involves converting filenames into hex, defining HTTP headers for file extensions, and converting HTML content into a hex array for efficient storage and retrieval. + + - **Automatic Generation of htmldata.c:** + - `makefsdata.c` creates `makefsdata.exe` automatically upon build, as specified in `CMakeLists.txt`. + - `makefsdata.exe` in turn generates `htmldata.c`, which links the HTML pages to lwIP for server-side processing, enabling seamless integration of web content. + +### Functionality and Implementation Details + +The WiFi (Web Server) Module offers the following functionalities: + +- **Dynamic Content Display:** SSI allows the server to include dynamic content in web pages, facilitating real-time updates or information display. + +- **User Interaction Handling:** CGI enables the server to process user interactions received from the web interface, enabling control and interaction with the Pico-based car. + +- **WiFi Connectivity:** `frontend.h` establishes a connection to the designated WiFi network using the environment variables WIFI_SSID and WIFI_PASSWORD. This connection is essential for enabling communication between the Pico device and the network. + + +The integration of SSI, CGI, and the makefsdata tool empowers the WiFi (Web Server) Module to provide a user-friendly interface for monitoring and controlling the Pico-based car within the defined network environment. + diff --git a/frtos/CMakeLists.txt b/frtos/CMakeLists.txt index 28447a9..a9730b9 100644 --- a/frtos/CMakeLists.txt +++ b/frtos/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(car) add_subdirectory(ultrasonic_sensor) add_subdirectory(magnetometer) add_subdirectory(frontend) +add_subdirectory(map) add_executable(rtos_car rtos_car.c) @@ -33,6 +34,7 @@ target_link_libraries(rtos_car hardware_adc hardware_pwm hardware_i2c + pico_lwip_http ) pico_enable_stdio_usb(rtos_car 1) pico_add_extra_outputs(rtos_car) diff --git a/frtos/config/car_config.h b/frtos/config/car_config.h index 13d6ca4..a27d7ad 100644 --- a/frtos/config/car_config.h +++ b/frtos/config/car_config.h @@ -6,7 +6,9 @@ #ifndef CAR_CONFIG_H #define CAR_CONFIG_H -#define PRIO (tskIDLE_PRIORITY + 1UL) +#define PRIO (tskIDLE_PRIORITY + 1UL) +#define MAX_HEIGHT 10 +#define MAX_WIDTH 10 typedef struct s_obs_struct { @@ -16,6 +18,28 @@ typedef struct s_obs_struct } obs_t; +typedef enum e_direction +{ + up, + down, + left, + right +} mapping_direction_t; + +typedef struct +{ + char type; + int reachable; + int visited; +} mazecells_t; + +typedef struct +{ + int height; + int width; + mazecells_t mazecells[MAX_HEIGHT][MAX_WIDTH]; +} maze_t; + typedef struct { obs_t *obs; @@ -23,6 +47,7 @@ typedef struct motor_t *p_right_motor; motor_pid_t *p_pid; direction_t *p_direction; + maze_t *p_maze; } car_struct_t; diff --git a/frtos/config/line_sensor_config.h b/frtos/config/line_sensor_config.h index d40bb60..ad70b8b 100644 --- a/frtos/config/line_sensor_config.h +++ b/frtos/config/line_sensor_config.h @@ -3,10 +3,10 @@ /* ADC Configuration */ -#define LINE_SENSOR_READ_DELAY ( 100 ) +#define LINE_SENSOR_READ_DELAY (100) -#define LEFT_SENSOR_PIN ( 26 ) -#define RIGHT_SENSOR_PIN ( 27 ) -#define BARCODE_SENSOR_PIN ( 22 ) +#define LEFT_SENSOR_PIN (26) +#define RIGHT_SENSOR_PIN (27) +#define BARCODE_SENSOR_PIN (22) -#endif //CONFIG_H +#endif // CONFIG_H diff --git a/frtos/frontend/CMakeLists.txt b/frtos/frontend/CMakeLists.txt index dd2581c..55a3c4b 100644 --- a/frtos/frontend/CMakeLists.txt +++ b/frtos/frontend/CMakeLists.txt @@ -28,5 +28,10 @@ target_include_directories(frontend PRIVATE ${CMAKE_CURRENT_LIST_DIR} ) +target_compile_definitions(frontend PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" +) + pico_enable_stdio_usb(frontend 1) pico_add_extra_outputs(frontend) \ No newline at end of file diff --git a/frtos/frontend/cgi.h b/frtos/frontend/cgi.h index 39a9f99..be28eac 100644 --- a/frtos/frontend/cgi.h +++ b/frtos/frontend/cgi.h @@ -2,6 +2,8 @@ #include "pico/cyw43_arch.h" #include "stdio.h" +#include "car_config.h" + // CGI handler for start/stop car const char * cgi_status_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { @@ -12,9 +14,17 @@ const char * cgi_status_handler(int iIndex, int iNumParams, char *pcParam[], cha if(strcmp(pcValue[0], "0") == 0){ cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); printf("CAR STOP"); // call car stop func + /* + set_wheel_direction(DIRECTION_MASK); + set_wheel_speed_synced(0u, car_struct); + */ }else if(strcmp(pcValue[0], "1") == 0){ cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); printf("CAR START"); // call car start func + /* + set_wheel_direction(DIRECTION_FORWARD); + set_wheel_speed_synced(90u, car_struct); + */ } } @@ -54,7 +64,8 @@ static const tCGI cgi_handlers[] = { }, }; -void cgi_init(void) -{ +void cgi_init(void *params) +{ + car_struct_t *car_struct = (car_struct_t *)params; http_set_cgi_handlers(cgi_handlers, 2); } \ No newline at end of file diff --git a/frtos/frontend/frontend.c b/frtos/frontend/frontend.c index f58a485..797fc33 100644 --- a/frtos/frontend/frontend.c +++ b/frtos/frontend/frontend.c @@ -1,71 +1,23 @@ -#include "lwip/apps/httpd.h" #include "pico/stdlib.h" -#include "pico/cyw43_arch.h" -#include "lwipopts.h" -#include "ssi.h" -#include "cgi.h" -#include "FreeRTOS.h" -#include "task.h" -// WIFI Credentials - take care if pushing to GitHub! -const char WIFI_SSID[] = "XXX"; -const char WIFI_PASSWORD[] = "XXX"; +#include "frontend.h" +#include "car_config.h" -void print_ip_address() { - struct netif *netif = netif_list; - while (netif != NULL) { - if (netif_is_up(netif)) { - printf("IP Address: %s\n", ipaddr_ntoa(&(netif->ip_addr))); - } - netif = netif->next; - } -} - -static void webserver_run(){ - // Initialize web server - httpd_init(); - printf("Http server initialized\n"); - - // Configure SSI and CGI handler - ssi_init(); - printf("SSI Handler initialized\n"); - cgi_init(); - printf("CGI Handler initialized\n"); - - // Infinite loop - while (1) { - // Add any required delay or use other FreeRTOS features here if needed - vTaskDelay(pdMS_TO_TICKS(1000)); - } -} -static void webserver_task(){ - if (cyw43_arch_init()) - { - printf("failed to initialise\n"); - return; - } - - cyw43_arch_enable_sta_mode(); - - printf("Connecting to WiFi...\n"); - - // Connect to the WiFI network - loop until connected - while (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0) { - printf("Attempting to connect...\n"); - } - // Print a success message once connected - printf("Connected! \n"); - print_ip_address(); - - webserver_run(); - cyw43_arch_deinit(); -} int main() { stdio_init_all(); - // Create the FreeRTOS task for the web server - TaskHandle_t task_handle; - xTaskCreate(webserver_task, "WebServerTask", configMINIMAL_STACK_SIZE, NULL, 1, &task_handle); + obs_t obs; + motor_t motor_right; + motor_t motor_left; + motor_pid_t pid; + + car_struct_t car_struct = { .p_right_motor = &motor_right, + .p_left_motor = &motor_left, + .p_pid = &pid, + .obs = &obs}; + + webserver_init(&car_struct); //to test webserver only, remove car_struct from all frontend files + // Start the FreeRTOS scheduler vTaskStartScheduler(); diff --git a/frtos/frontend/frontend.h b/frtos/frontend/frontend.h new file mode 100644 index 0000000..245298d --- /dev/null +++ b/frtos/frontend/frontend.h @@ -0,0 +1,75 @@ +#include "lwip/apps/httpd.h" +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "lwipopts.h" +#include "ssi.h" +#include "cgi.h" +#include "FreeRTOS.h" +#include "task.h" + +#include "car_config.h" + +// print assigned ip addr +void print_ip_address() { + struct netif *netif = netif_list; + while (netif != NULL) { + if (netif_is_up(netif)) { + printf("IP Address: %s\n", ipaddr_ntoa(&(netif->ip_addr))); + } + netif = netif->next; + } +} + +// initializes server and handlers +static void webserver_run(void *params){ + car_struct_t *car_struct = (car_struct_t *)params; + + // Initialize web server + httpd_init(); + printf("Http server initialized\n"); + + // Configure SSI and CGI handler + ssi_init(&car_struct); + printf("SSI Handler initialized\n"); + cgi_init(&car_struct); + printf("CGI Handler initialized\n"); + + // Infinite loop + while (1) { + // Add any required delay or use other FreeRTOS features here if needed + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} +// webserver task connect wifi & start webserver_run +static void webserver_task(void *params){ + car_struct_t *car_struct = (car_struct_t *)params; + if (cyw43_arch_init()) + { + printf("failed to initialise\n"); + return; + } + + cyw43_arch_enable_sta_mode(); + + printf("Connecting to WiFi...\n"); + + // Connect to the WiFI network - loop until connected + while (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0) { + printf("Attempting to connect...\n"); + } + // Print a success message once connected + printf("Connected! \n"); + print_ip_address(); + + webserver_run(&car_struct); + cyw43_arch_deinit(); +} + +// frontend initialize - create task +void webserver_init(void *params) +{ + car_struct_t *car_struct = (car_struct_t *)params; + // Create the FreeRTOS task for the web server + TaskHandle_t task_handle; + xTaskCreate(webserver_task, "WebServerTask", configMINIMAL_STACK_SIZE, (void *)&car_struct, 1, &task_handle); +} \ No newline at end of file diff --git a/frtos/frontend/html_files/index.shtml b/frtos/frontend/html_files/index.shtml index a30f9a3..25cadf9 100644 --- a/frtos/frontend/html_files/index.shtml +++ b/frtos/frontend/html_files/index.shtml @@ -54,6 +54,11 @@ margin-top: 20px; } + .obstacle-output { + font-size: 24px; + margin-top: 20px; + } + .orientation-output { font-size: 24px; margin-top: 20px; @@ -92,6 +97,9 @@

PID Controller Output:

+
+

Obstacle:

+

Orientation:

diff --git a/frtos/frontend/htmldata.c b/frtos/frontend/htmldata.c index 8faf14a..26b4f05 100644 --- a/frtos/frontend/htmldata.c +++ b/frtos/frontend/htmldata.c @@ -125,258 +125,279 @@ static const unsigned char data_index_shtml[] = { 0x6F, 0x70, 0x3A, 0x20, 0x32, 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x2E, 0x6F, 0x72, 0x69, 0x65, 0x6E, 0x74, 0x61, - 0x74, 0x69, 0x6F, 0x6E, 0x2D, 0x6F, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x20, 0x7B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6F, 0x6E, - 0x74, 0x2D, 0x73, 0x69, 0x7A, 0x65, 0x3A, 0x20, 0x32, 0x34, - 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6D, 0x61, 0x72, - 0x67, 0x69, 0x6E, 0x2D, 0x74, 0x6F, 0x70, 0x3A, 0x20, 0x32, - 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2E, 0x6D, 0x61, - 0x7A, 0x65, 0x2D, 0x6D, 0x61, 0x70, 0x20, 0x7B, 0x0D, 0x0A, + 0x20, 0x20, 0x2E, 0x6F, 0x62, 0x73, 0x74, 0x61, 0x63, 0x6C, + 0x65, 0x2D, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x7B, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6F, 0x6E, 0x74, 0x2D, 0x73, + 0x69, 0x7A, 0x65, 0x3A, 0x20, 0x32, 0x34, 0x70, 0x78, 0x3B, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x6D, 0x61, 0x72, 0x67, 0x69, 0x6E, + 0x2D, 0x74, 0x6F, 0x70, 0x3A, 0x20, 0x32, 0x30, 0x70, 0x78, + 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x2E, 0x6F, 0x72, 0x69, 0x65, 0x6E, + 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2D, 0x6F, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x20, 0x7B, 0x0D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6F, 0x6E, 0x74, 0x2D, 0x73, 0x69, 0x7A, 0x65, 0x3A, 0x20, + 0x32, 0x34, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6D, + 0x61, 0x72, 0x67, 0x69, 0x6E, 0x2D, 0x74, 0x6F, 0x70, 0x3A, + 0x20, 0x32, 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x0D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2E, + 0x6D, 0x61, 0x7A, 0x65, 0x2D, 0x6D, 0x61, 0x70, 0x20, 0x7B, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x6D, 0x61, 0x72, 0x67, 0x69, 0x6E, + 0x2D, 0x74, 0x6F, 0x70, 0x3A, 0x20, 0x32, 0x30, 0x70, 0x78, + 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3A, 0x20, 0x32, 0x30, 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6D, 0x61, 0x72, 0x67, 0x69, 0x6E, 0x2D, 0x74, - 0x6F, 0x70, 0x3A, 0x20, 0x32, 0x30, 0x70, 0x78, 0x3B, 0x0D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3A, 0x20, + 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3A, 0x20, 0x32, 0x30, 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3A, 0x20, 0x32, 0x30, - 0x30, 0x70, 0x78, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6F, - 0x72, 0x64, 0x65, 0x72, 0x3A, 0x20, 0x31, 0x70, 0x78, 0x20, - 0x73, 0x6F, 0x6C, 0x69, 0x64, 0x20, 0x23, 0x33, 0x33, 0x33, - 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, - 0x73, 0x74, 0x79, 0x6C, 0x65, 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, - 0x68, 0x65, 0x61, 0x64, 0x3E, 0x0D, 0x0A, 0x3C, 0x62, 0x6F, - 0x64, 0x79, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, - 0x68, 0x31, 0x3E, 0x43, 0x61, 0x72, 0x20, 0x43, 0x6F, 0x6E, - 0x74, 0x72, 0x6F, 0x6C, 0x20, 0x50, 0x61, 0x6E, 0x65, 0x6C, - 0x3C, 0x2F, 0x68, 0x31, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, - 0x73, 0x3D, 0x22, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, - 0x65, 0x72, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, - 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x74, 0x69, 0x6D, 0x65, - 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, 0x20, 0x69, - 0x64, 0x3D, 0x22, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, - 0x2D, 0x74, 0x69, 0x6D, 0x65, 0x22, 0x3E, 0x30, 0x20, 0x73, - 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x3C, 0x2F, 0x70, 0x3E, - 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, + 0x62, 0x6F, 0x72, 0x64, 0x65, 0x72, 0x3A, 0x20, 0x31, 0x70, + 0x78, 0x20, 0x73, 0x6F, 0x6C, 0x69, 0x64, 0x20, 0x23, 0x33, + 0x33, 0x33, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x3C, 0x2F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x3E, 0x0D, 0x0A, + 0x3C, 0x2F, 0x68, 0x65, 0x61, 0x64, 0x3E, 0x0D, 0x0A, 0x3C, + 0x62, 0x6F, 0x64, 0x79, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x3C, 0x68, 0x31, 0x3E, 0x43, 0x61, 0x72, 0x20, 0x43, + 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x20, 0x50, 0x61, 0x6E, + 0x65, 0x6C, 0x3C, 0x2F, 0x68, 0x31, 0x3E, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, + 0x61, 0x73, 0x73, 0x3D, 0x22, 0x63, 0x6F, 0x6E, 0x74, 0x61, + 0x69, 0x6E, 0x65, 0x72, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, - 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x62, 0x75, - 0x74, 0x74, 0x6F, 0x6E, 0x73, 0x22, 0x3E, 0x0D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 0x22, - 0x2F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2E, 0x63, 0x67, - 0x69, 0x3F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3D, 0x31, - 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, - 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x62, 0x75, - 0x74, 0x74, 0x6F, 0x6E, 0x22, 0x20, 0x3E, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, - 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0D, + 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x74, 0x69, + 0x6D, 0x65, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, + 0x20, 0x69, 0x64, 0x3D, 0x22, 0x65, 0x6C, 0x61, 0x70, 0x73, + 0x65, 0x64, 0x2D, 0x74, 0x69, 0x6D, 0x65, 0x22, 0x3E, 0x30, + 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x3C, 0x2F, + 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, + 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, + 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x73, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 0x22, 0x2F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2E, 0x63, 0x67, 0x69, 0x3F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x3D, 0x30, 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, + 0x3D, 0x31, 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x22, 0x20, 0x3E, 0x53, - 0x74, 0x6F, 0x70, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, - 0x6E, 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, - 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, - 0x73, 0x73, 0x3D, 0x22, 0x73, 0x70, 0x65, 0x65, 0x64, 0x22, - 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, 0x3E, 0x53, 0x70, - 0x65, 0x65, 0x64, 0x3A, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x23, - 0x73, 0x70, 0x65, 0x65, 0x64, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, - 0x73, 0x70, 0x61, 0x6E, 0x3E, 0x3C, 0x2F, 0x70, 0x3E, 0x0D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, - 0x61, 0x73, 0x73, 0x3D, 0x22, 0x73, 0x70, 0x65, 0x65, 0x64, - 0x2D, 0x63, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x22, 0x3E, - 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x61, - 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 0x22, 0x2F, 0x73, 0x70, - 0x65, 0x65, 0x64, 0x2E, 0x63, 0x67, 0x69, 0x3F, 0x73, 0x70, - 0x65, 0x65, 0x64, 0x3D, 0x30, 0x22, 0x20, 0x3E, 0x3C, 0x62, - 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x63, 0x6C, 0x61, 0x73, - 0x73, 0x3D, 0x22, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x22, - 0x20, 0x3E, 0x2D, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, - 0x6E, 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, + 0x74, 0x61, 0x72, 0x74, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, + 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, - 0x66, 0x3D, 0x22, 0x2F, 0x73, 0x70, 0x65, 0x65, 0x64, 0x2E, - 0x63, 0x67, 0x69, 0x3F, 0x73, 0x70, 0x65, 0x65, 0x64, 0x3D, - 0x31, 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, - 0x6E, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x62, - 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x22, 0x20, 0x3E, 0x2B, 0x3C, - 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, - 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, - 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x61, 0x20, 0x68, 0x72, + 0x65, 0x66, 0x3D, 0x22, 0x2F, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x2E, 0x63, 0x67, 0x69, 0x3F, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x3D, 0x30, 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, + 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, + 0x3D, 0x22, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x22, 0x20, + 0x3E, 0x53, 0x74, 0x6F, 0x70, 0x3C, 0x2F, 0x62, 0x75, 0x74, + 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, + 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, + 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x73, 0x70, 0x65, 0x65, + 0x64, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, 0x3E, + 0x53, 0x70, 0x65, 0x65, 0x64, 0x3A, 0x20, 0x3C, 0x21, 0x2D, + 0x2D, 0x23, 0x73, 0x70, 0x65, 0x65, 0x64, 0x2D, 0x2D, 0x3E, + 0x3C, 0x2F, 0x73, 0x70, 0x61, 0x6E, 0x3E, 0x3C, 0x2F, 0x70, + 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, + 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x73, 0x70, 0x65, + 0x65, 0x64, 0x2D, 0x63, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, + 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 0x22, 0x2F, + 0x73, 0x70, 0x65, 0x65, 0x64, 0x2E, 0x63, 0x67, 0x69, 0x3F, + 0x73, 0x70, 0x65, 0x65, 0x64, 0x3D, 0x30, 0x22, 0x20, 0x3E, + 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x63, 0x6C, + 0x61, 0x73, 0x73, 0x3D, 0x22, 0x62, 0x75, 0x74, 0x74, 0x6F, + 0x6E, 0x22, 0x20, 0x3E, 0x2D, 0x3C, 0x2F, 0x62, 0x75, 0x74, + 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x61, 0x20, 0x68, + 0x72, 0x65, 0x66, 0x3D, 0x22, 0x2F, 0x73, 0x70, 0x65, 0x65, + 0x64, 0x2E, 0x63, 0x67, 0x69, 0x3F, 0x73, 0x70, 0x65, 0x65, + 0x64, 0x3D, 0x31, 0x22, 0x20, 0x3E, 0x3C, 0x62, 0x75, 0x74, + 0x74, 0x6F, 0x6E, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, + 0x22, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x22, 0x20, 0x3E, + 0x2B, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x3E, + 0x3C, 0x2F, 0x61, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, + 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, + 0x3D, 0x22, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3E, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, 0x3E, 0x42, 0x61, 0x72, + 0x63, 0x6F, 0x64, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6E, 0x6E, + 0x65, 0x72, 0x20, 0x4F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3A, + 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x23, 0x62, 0x61, 0x72, 0x63, + 0x6F, 0x64, 0x65, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, 0x73, 0x70, + 0x61, 0x6E, 0x3E, 0x3C, 0x2F, 0x70, 0x3E, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, + 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, + 0x61, 0x73, 0x73, 0x3D, 0x22, 0x70, 0x69, 0x64, 0x2D, 0x6F, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3E, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x3C, 0x70, 0x3E, 0x50, 0x49, 0x44, 0x20, 0x43, 0x6F, + 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72, 0x20, 0x4F, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x3A, 0x20, 0x3C, 0x21, 0x2D, + 0x2D, 0x23, 0x70, 0x69, 0x64, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, + 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, - 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3E, 0x0D, 0x0A, + 0x6F, 0x62, 0x73, 0x74, 0x61, 0x63, 0x6C, 0x65, 0x2D, 0x6F, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3C, 0x70, 0x3E, 0x42, 0x61, 0x72, 0x63, 0x6F, - 0x64, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6E, 0x6E, 0x65, 0x72, - 0x20, 0x4F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3A, 0x20, 0x3C, - 0x21, 0x2D, 0x2D, 0x23, 0x62, 0x61, 0x72, 0x63, 0x6F, 0x64, - 0x65, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, 0x73, 0x70, 0x61, 0x6E, - 0x3E, 0x3C, 0x2F, 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, - 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, - 0x73, 0x3D, 0x22, 0x70, 0x69, 0x64, 0x2D, 0x6F, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, - 0x70, 0x3E, 0x50, 0x49, 0x44, 0x20, 0x43, 0x6F, 0x6E, 0x74, - 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72, 0x20, 0x4F, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x3A, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x23, - 0x70, 0x69, 0x64, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, 0x70, 0x3E, + 0x20, 0x3C, 0x70, 0x3E, 0x4F, 0x62, 0x73, 0x74, 0x61, 0x63, + 0x6C, 0x65, 0x3A, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x23, 0x6F, + 0x62, 0x73, 0x74, 0x61, 0x63, 0x6C, 0x65, 0x2D, 0x2D, 0x3E, + 0x3C, 0x2F, 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, + 0x3D, 0x22, 0x6F, 0x72, 0x69, 0x65, 0x6E, 0x74, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x2D, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x70, 0x3E, 0x4F, + 0x72, 0x69, 0x65, 0x6E, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, + 0x3A, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x23, 0x6F, 0x72, 0x69, + 0x65, 0x6E, 0x74, 0x2D, 0x2D, 0x3E, 0x3C, 0x2F, 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, - 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x6F, 0x72, - 0x69, 0x65, 0x6E, 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2D, - 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3E, 0x0D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3C, 0x70, 0x3E, 0x4F, 0x72, 0x69, 0x65, 0x6E, - 0x74, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x3C, 0x21, - 0x2D, 0x2D, 0x23, 0x6F, 0x72, 0x69, 0x65, 0x6E, 0x74, 0x2D, - 0x2D, 0x3E, 0x3C, 0x2F, 0x70, 0x3E, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, - 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, - 0x73, 0x73, 0x3D, 0x22, 0x6D, 0x61, 0x7A, 0x65, 0x2D, 0x6D, - 0x61, 0x70, 0x22, 0x20, 0x69, 0x64, 0x3D, 0x22, 0x6D, 0x61, - 0x7A, 0x65, 0x2D, 0x6D, 0x61, 0x70, 0x22, 0x3E, 0x0D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x20, 0x6D, 0x61, 0x7A, - 0x65, 0x20, 0x6D, 0x61, 0x70, 0x20, 0x70, 0x61, 0x72, 0x74, - 0x20, 0x2D, 0x2D, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, - 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, - 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x3E, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x46, - 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, - 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6E, - 0x64, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79, 0x20, - 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x20, 0x74, 0x69, - 0x6D, 0x65, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x66, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6C, 0x61, - 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65, 0x28, 0x29, - 0x20, 0x7B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x43, - 0x68, 0x65, 0x63, 0x6B, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x74, 0x69, - 0x6D, 0x65, 0x20, 0x69, 0x73, 0x20, 0x73, 0x74, 0x6F, 0x72, - 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x6C, 0x6F, 0x63, 0x61, - 0x6C, 0x53, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x0D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6C, 0x65, 0x74, 0x20, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x6C, 0x6F, - 0x63, 0x61, 0x6C, 0x53, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, - 0x2E, 0x67, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6D, 0x28, 0x27, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x27, - 0x29, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, - 0x20, 0x28, 0x21, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, - 0x6D, 0x65, 0x29, 0x20, 0x7B, 0x0D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x49, 0x66, 0x20, 0x6E, - 0x6F, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x74, 0x69, - 0x6D, 0x65, 0x20, 0x69, 0x73, 0x20, 0x73, 0x74, 0x6F, 0x72, - 0x65, 0x64, 0x2C, 0x20, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, - 0x74, 0x69, 0x6D, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x74, 0x69, - 0x6D, 0x65, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x6D, 0x61, + 0x7A, 0x65, 0x2D, 0x6D, 0x61, 0x70, 0x22, 0x20, 0x69, 0x64, + 0x3D, 0x22, 0x6D, 0x61, 0x7A, 0x65, 0x2D, 0x6D, 0x61, 0x70, + 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x21, 0x2D, 0x2D, + 0x20, 0x6D, 0x61, 0x7A, 0x65, 0x20, 0x6D, 0x61, 0x70, 0x20, + 0x70, 0x61, 0x72, 0x74, 0x20, 0x2D, 0x2D, 0x3E, 0x0D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, + 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x3C, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3E, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2F, 0x2F, 0x20, 0x46, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x74, 0x6F, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x64, 0x69, 0x73, 0x70, + 0x6C, 0x61, 0x79, 0x20, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, + 0x64, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x0D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6E, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x45, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, + 0x6D, 0x65, 0x28, 0x29, 0x20, 0x7B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2F, 0x2F, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6B, 0x20, 0x69, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x73, 0x74, 0x6F, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, + 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x53, 0x74, 0x6F, 0x72, 0x61, + 0x67, 0x65, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6C, 0x65, 0x74, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x20, - 0x3D, 0x20, 0x44, 0x61, 0x74, 0x65, 0x2E, 0x6E, 0x6F, 0x77, - 0x28, 0x29, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3D, 0x20, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x53, 0x74, 0x6F, + 0x72, 0x61, 0x67, 0x65, 0x2E, 0x67, 0x65, 0x74, 0x49, 0x74, + 0x65, 0x6D, 0x28, 0x27, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6D, 0x65, 0x27, 0x29, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x53, 0x74, 0x6F, 0x72, - 0x61, 0x67, 0x65, 0x2E, 0x73, 0x65, 0x74, 0x49, 0x74, 0x65, - 0x6D, 0x28, 0x27, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, - 0x6D, 0x65, 0x27, 0x2C, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x54, 0x69, 0x6D, 0x65, 0x29, 0x3B, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x21, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x29, 0x20, 0x7B, 0x0D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, - 0x43, 0x61, 0x6C, 0x63, 0x75, 0x6C, 0x61, 0x74, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, - 0x64, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x20, 0x69, 0x6E, 0x20, - 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x0D, 0x0A, 0x20, + 0x49, 0x66, 0x20, 0x6E, 0x6F, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x73, 0x74, 0x6F, 0x72, 0x65, 0x64, 0x2C, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6E, 0x74, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x20, 0x61, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x20, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6E, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x3D, - 0x20, 0x44, 0x61, 0x74, 0x65, 0x2E, 0x6E, 0x6F, 0x77, 0x28, - 0x29, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6F, 0x6E, 0x73, - 0x74, 0x20, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, - 0x69, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x4D, 0x61, 0x74, 0x68, - 0x2E, 0x66, 0x6C, 0x6F, 0x6F, 0x72, 0x28, 0x28, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6E, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x20, - 0x2D, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6D, - 0x65, 0x29, 0x20, 0x2F, 0x20, 0x31, 0x30, 0x30, 0x30, 0x29, - 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x44, 0x61, 0x74, 0x65, + 0x2E, 0x6E, 0x6F, 0x77, 0x28, 0x29, 0x3B, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6C, 0x6F, 0x63, 0x61, 0x6C, + 0x53, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x2E, 0x73, 0x65, + 0x74, 0x49, 0x74, 0x65, 0x6D, 0x28, 0x27, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x27, 0x2C, 0x20, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6D, 0x65, 0x29, 0x3B, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2F, 0x2F, 0x20, 0x43, 0x61, 0x6C, 0x63, 0x75, 0x6C, + 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6C, + 0x61, 0x70, 0x73, 0x65, 0x64, 0x20, 0x74, 0x69, 0x6D, 0x65, + 0x20, 0x69, 0x6E, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, + 0x73, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x54, 0x69, + 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x44, 0x61, 0x74, 0x65, 0x2E, + 0x6E, 0x6F, 0x77, 0x28, 0x29, 0x3B, 0x0D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x20, 0x65, 0x6C, 0x61, 0x70, + 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x3D, 0x20, + 0x4D, 0x61, 0x74, 0x68, 0x2E, 0x66, 0x6C, 0x6F, 0x6F, 0x72, + 0x28, 0x28, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x54, + 0x69, 0x6D, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x54, 0x69, 0x6D, 0x65, 0x29, 0x20, 0x2F, 0x20, 0x31, + 0x30, 0x30, 0x30, 0x29, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2F, 0x2F, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6C, + 0x61, 0x79, 0x65, 0x64, 0x20, 0x65, 0x6C, 0x61, 0x70, 0x73, + 0x65, 0x64, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x0D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x64, 0x6F, 0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x2E, + 0x67, 0x65, 0x74, 0x45, 0x6C, 0x65, 0x6D, 0x65, 0x6E, 0x74, + 0x42, 0x79, 0x49, 0x64, 0x28, 0x27, 0x65, 0x6C, 0x61, 0x70, + 0x73, 0x65, 0x64, 0x2D, 0x74, 0x69, 0x6D, 0x65, 0x27, 0x29, + 0x2E, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x74, 0x65, + 0x6E, 0x74, 0x20, 0x3D, 0x20, 0x60, 0x24, 0x7B, 0x65, 0x6C, + 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65, 0x7D, + 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x60, 0x3B, + 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x43, 0x61, 0x6C, 0x6C, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x45, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, + 0x6D, 0x65, 0x20, 0x66, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x77, 0x68, 0x65, 0x6E, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x61, 0x67, 0x65, 0x20, 0x6C, 0x6F, 0x61, 0x64, + 0x73, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x77, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x2E, 0x6F, 0x6E, + 0x6C, 0x6F, 0x61, 0x64, 0x20, 0x3D, 0x20, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x45, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, + 0x54, 0x69, 0x6D, 0x65, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x64, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x64, 0x20, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x20, 0x74, - 0x69, 0x6D, 0x65, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6F, 0x63, - 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x2E, 0x67, 0x65, 0x74, 0x45, - 0x6C, 0x65, 0x6D, 0x65, 0x6E, 0x74, 0x42, 0x79, 0x49, 0x64, - 0x28, 0x27, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x2D, - 0x74, 0x69, 0x6D, 0x65, 0x27, 0x29, 0x2E, 0x74, 0x65, 0x78, - 0x74, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x20, 0x3D, - 0x20, 0x60, 0x24, 0x7B, 0x65, 0x6C, 0x61, 0x70, 0x73, 0x65, - 0x64, 0x54, 0x69, 0x6D, 0x65, 0x7D, 0x20, 0x73, 0x65, 0x63, - 0x6F, 0x6E, 0x64, 0x73, 0x60, 0x3B, 0x0D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0D, 0x0A, 0x0D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, - 0x2F, 0x20, 0x43, 0x61, 0x6C, 0x6C, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6C, 0x61, - 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x66, - 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x77, 0x68, - 0x65, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x67, - 0x65, 0x20, 0x6C, 0x6F, 0x61, 0x64, 0x73, 0x0D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x6E, - 0x64, 0x6F, 0x77, 0x2E, 0x6F, 0x6E, 0x6C, 0x6F, 0x61, 0x64, - 0x20, 0x3D, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, - 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65, - 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x2F, 0x2F, 0x20, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6C, 0x61, - 0x70, 0x73, 0x65, 0x64, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x20, - 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, 0x73, 0x65, 0x63, 0x6F, - 0x6E, 0x64, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6C, 0x28, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x45, 0x6C, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6D, - 0x65, 0x2C, 0x20, 0x31, 0x30, 0x30, 0x30, 0x29, 0x3B, 0x0D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, - 0x2F, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3E, 0x0D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, - 0x62, 0x6F, 0x64, 0x79, 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, 0x68, - 0x74, 0x6D, 0x6C, 0x3E, 0x0D, 0x0A, }; + 0x69, 0x6D, 0x65, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, + 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x0D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x49, + 0x6E, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6C, 0x28, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x45, 0x6C, 0x61, 0x70, 0x73, 0x65, + 0x64, 0x54, 0x69, 0x6D, 0x65, 0x2C, 0x20, 0x31, 0x30, 0x30, + 0x30, 0x29, 0x3B, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x3E, 0x0D, + 0x0A, 0x3C, 0x2F, 0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0D, 0x0A, + }; const struct fsdata_file file_index_shtml[] = {{ NULL, data_index_shtml, data_index_shtml + 13, sizeof(data_index_shtml) - 13, FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT }}; diff --git a/frtos/frontend/makefsdata.exe b/frtos/frontend/makefsdata.exe index 30f333c..f242e1f 100644 Binary files a/frtos/frontend/makefsdata.exe and b/frtos/frontend/makefsdata.exe differ diff --git a/frtos/frontend/ssi.h b/frtos/frontend/ssi.h index df1e5b0..1de109e 100644 --- a/frtos/frontend/ssi.h +++ b/frtos/frontend/ssi.h @@ -3,8 +3,11 @@ #include "hardware/adc.h" #include "pico/stdlib.h" +#include "car_config.h" +car_struct_t *car_struct; + // SSI tags - tag length limited to 8 bytes by default -const char * ssi_tags[] = {"speed","barcode","pid","orient"}; +const char * ssi_tags[] = {"speed","barcode","pid","obstacle","orient"}; u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { size_t printed; @@ -12,8 +15,15 @@ u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { case 0: // Speed { // call getSpeed() function - const float Speed = adc_read() * 3.3f / (1 << 12); - printed = snprintf(pcInsert, iInsertLen, "%f ",Speed); + + /* + const float c_speed = car_struct->p_right_motor->speed.current_cms; + printf("\n\t%f\n",c_speed); + printed = snprintf(pcInsert, iInsertLen, "%f ",c_speed); + */ + + const float speed = adc_read() * 3.3f / (1 << 12); + printed = snprintf(pcInsert, iInsertLen, "%f ",speed); } break; case 1: // barcode @@ -25,15 +35,32 @@ u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { break; case 2: //PID { - // whatever to display for PID - const char* PID = "54" ; + // whatever to display for PID + const char* PID = "-" ; printed = snprintf(pcInsert, iInsertLen, "%s", PID); } break; - case 3: //Orientation + case 3: //Obstacle detected + { + // call getObstacleDetected() function + + /* + bool obstacle = car_struct->obs->ultrasonic_detected; + if (obstacle){ + printed = snprintf(pcInsert, iInsertLen, "%s", "YES"); + } + else{ + printed = snprintf(pcInsert, iInsertLen, "%s", "NO"); + } + */ + + printed = snprintf(pcInsert, iInsertLen, "%s", "YES"); + } + case 4: //Orientation { // call getOrientation() function - const char* orien = "South" ; + + const char* orien = "South"; printed = snprintf(pcInsert, iInsertLen, "%s", orien); } break; @@ -46,7 +73,10 @@ u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { } // Initialise the SSI handler -void ssi_init() { +void ssi_init(void *params) { + + car_struct = (car_struct_t *)params; + // Initialise ADC (internal pin) adc_init(); adc_set_temp_sensor_enabled(true); diff --git a/frtos/line_sensor/line_sensor_init.h b/frtos/line_sensor/line_sensor_init.h index 1388f37..dfec0ea 100644 --- a/frtos/line_sensor/line_sensor_init.h +++ b/frtos/line_sensor/line_sensor_init.h @@ -31,7 +31,8 @@ SemaphoreHandle_t g_left_sensor_sem = NULL; static inline void line_sensor_init(car_struct_t *p_car_struct) { - p_car_struct->obs->left_sensor_detected, p_car_struct->obs->left_sensor_detected = false; + p_car_struct->obs->left_sensor_detected, + p_car_struct->obs->left_sensor_detected = false; g_left_sensor_sem = xSemaphoreCreateBinary(); @@ -40,7 +41,6 @@ line_sensor_init(car_struct_t *p_car_struct) // Initialise 3 GPIO pins and set them to input gpio_init_mask(mask); gpio_set_dir_in_masked(mask); - } /** @@ -48,35 +48,37 @@ line_sensor_init(car_struct_t *p_car_struct) * @param rt * @return True (To keep the timer running) */ -bool h_left_sensor_timer_handler(repeating_timer_t *repeatingTimer) { +bool +h_left_sensor_timer_handler(repeating_timer_t *repeatingTimer) +{ BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(g_left_sensor_sem, - &xHigherPriorityTaskWoken); + xSemaphoreGiveFromISR(g_left_sensor_sem, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); return true; } - void -monitor_line_sensor_task(void *pvParameters) { +monitor_line_sensor_task(void *pvParameters) +{ volatile obs_t *p_obs = NULL; - p_obs = (obs_t *) pvParameters; + p_obs = (obs_t *)pvParameters; for (;;) { -// if (xSemaphoreTake(g_left_sensor_sem, portMAX_DELAY) == pdTRUE) -// { - // Set the flag to notify the task - p_obs->left_sensor_detected = gpio_get(LEFT_SENSOR_PIN); - p_obs->right_sensor_detected = gpio_get(RIGHT_SENSOR_PIN); - // printf("Left Sensor: %d\n", p_obs->line_detected); - vTaskDelay(pdMS_TO_TICKS(LINE_SENSOR_READ_DELAY)); -// } + // Set the flag to notify the task + p_obs->left_sensor_detected = gpio_get(LEFT_SENSOR_PIN); + p_obs->right_sensor_detected = gpio_get(RIGHT_SENSOR_PIN); + + vTaskDelay(pdMS_TO_TICKS(LINE_SENSOR_READ_DELAY)); } } +/** + * @brief Initialise the tasks for the line sensor + * @param car_struct The car struct + */ void line_tasks_init(car_struct_t *car_struct) { @@ -89,5 +91,4 @@ line_tasks_init(car_struct_t *car_struct) &h_monitor_line_sensor_task); } - #endif /* LINE_SENSOR_INIT_H */ diff --git a/frtos/line_sensor/line_sensor_test.c b/frtos/line_sensor/line_sensor_test.c index a712c51..387794e 100644 --- a/frtos/line_sensor/line_sensor_test.c +++ b/frtos/line_sensor/line_sensor_test.c @@ -2,7 +2,6 @@ #include "line_sensor_init.h" #include "car_config.h" - int main(void) { @@ -10,7 +9,7 @@ main(void) obs_t obs; - car_struct_t car_struct = {.obs = &obs}; + car_struct_t car_struct = { .obs = &obs }; sleep_ms(2000); diff --git a/frtos/line_sensor/line_sensor_unused.c b/frtos/line_sensor/line_sensor_unused.c deleted file mode 100644 index 91a2ba1..0000000 --- a/frtos/line_sensor/line_sensor_unused.c +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include "pico/stdlib.h" - -#include "hardware/adc.h" - -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" -#include "semphr.h" - -#include "line_sensor.h" -#include "string.h" - -//const float conversionFactor = 3.3f / (1 << 12); - -volatile u_int8_t map[MAP_SIZE][MAP_SIZE] = {0}; - - -/** - * @brief Update the map based on the car's state - * - * @param car_state The current car state - */ -static inline void -update_map(car_state_t car_state) { - if (car_state.x >= 0 && car_state.x < MAP_SIZE && - car_state.y >= 0 && car_state.y < MAP_SIZE) { - map[car_state.x][car_state.y] = 1; - } -} - -/** - * @brief Handle forward movement of the car - * - * @param car_state The current car state - */ -static void -handle_forward_movement(car_state_t *car_state) { - printf("FORWARD, "); - // TODO: Check car's actual forward movement - switch (car_state->orientation) { - case NORTH: - printf("NORTH\n"); - car_state->y++; - break; - case EAST: - printf("EAST\n"); - car_state->x++; - break; - case SOUTH: - printf("SOUTH\n"); - car_state->y--; - break; - case WEST: - printf("WEST\n"); - car_state->x--; - break; - } -} - -/** - * @brief Handle a right turn of the car - * - * Note: Bitwise AND with 0x03 to ensure that the orientation - * is always between 0 and 3 - * @param car_state The current car state - */ -static inline void -handle_right_turn(car_state_t *car_state) { - car_state->orientation = (car_state->orientation + 1) & 0x03; -} - -/** - * @brief Handle a left turn of the car - * - * @param car_state The current car state - */ -static inline void -handle_left_turn(car_state_t *car_state) { - car_state->orientation = (car_state->orientation - 1) & 0x03; -} - -/** - * @brief Print the map to the console - * - * This function will print the map to the console - */ -void -print_map() { - // Invert the map, 0,0 is at the bottom left - for (int i = MAP_SIZE - 1; i >= 0; i --) - { - for (int j = 0; j < MAP_SIZE; j ++) - { - printf("%d ", map[j][i]); - } - printf("\n"); - } -} - diff --git a/frtos/magnetometer/LSM303DLHC_register.h b/frtos/magnetometer/LSM303DLHC_register.h index 8b24e6b..dbee727 100644 --- a/frtos/magnetometer/LSM303DLHC_register.h +++ b/frtos/magnetometer/LSM303DLHC_register.h @@ -28,8 +28,6 @@ #define LSM303_TEMP_OUT_H_M 0x31 #define LSM303_TEMP_OUT_L_M 0x32 - - #define ACCEL_ADDR 0x19 #define MAG_ADDR 0x1E diff --git a/frtos/magnetometer/magnetometer_direction.h b/frtos/magnetometer/magnetometer_direction.h index f817334..71e8163 100644 --- a/frtos/magnetometer/magnetometer_direction.h +++ b/frtos/magnetometer/magnetometer_direction.h @@ -9,11 +9,15 @@ * The yaw is calculated using the magnetometer and accelerometer data * The roll, pitch and yaw are combined to calculate the direction * of the car with a complementary filter and compensating for the - * temperature. + * temperature. (existing) * * The complementary filter is used to combine the accelerometer * and magnetometer data (yaw) to calculate the direction of the car * + * Complementary Filter was used previously, but it was not accurate + * enough. The yaw calculated from the magnetometer data was used + * instead. + * * Source: * https://www.nxp.com/docs/en/application-note/AN3461.pdf * https://ahrs.readthedocs.io/en/latest/filters/complementary.html @@ -80,7 +84,8 @@ calculate_yaw_magnetometer(int16_t magnetometer[3]) * @return Compensated Yaw */ float -compensate_magnetometer(float yaw_mag, int16_t temperature) { +compensate_magnetometer(float yaw_mag, int16_t temperature) +{ // Calculate temperature difference from the reference temperature uint delta_temp = temperature - TEMPERATURE_OFFSET; @@ -190,11 +195,11 @@ calculate_compass_direction(float yaw) * @param compass_direction Compass Direction */ static inline void -update_orientation_data(float roll, - float pitch, - float yaw, - compass_direction_t compass_direction, - volatile direction_t *g_direction) +update_orientation_data(float roll, + float pitch, + float yaw, + compass_direction_t compass_direction, + volatile direction_t *g_direction) { g_direction->roll = roll; g_direction->roll_angle = (roll > 0) ? LEFT : RIGHT; @@ -211,8 +216,8 @@ update_orientation_data(float roll, * @param magnetometer Magnetometer Data */ static void -read_direction(int16_t acceleration[3], - int16_t magnetometer[3], +read_direction(int16_t acceleration[3], + int16_t magnetometer[3], volatile direction_t *g_direction) { @@ -301,7 +306,7 @@ print_roll_and_pitch(angle_t roll_angle, angle_t pitch_angle) } void -updateDirection(volatile direction_t * g_direction) +updateDirection(volatile direction_t *g_direction) { int16_t magnetometer[3]; int16_t accelerometer[3]; @@ -317,53 +322,13 @@ updateDirection(volatile direction_t * g_direction) read_direction(accelerometer, magnetometer, g_direction); print_orientation_data(*g_direction); -// -// switch (g_direction->orientation) -// { -// case NORTH: -// cur_y++; -// break; -// case EAST: -// cur_x++; -// break; -// case SOUTH: -// cur_y--; -// break; -// case WEST: -// cur_x--; -// break; -// case NORTH_EAST: -// cur_x++; -// cur_y++; -// break; -// case SOUTH_EAST: -// cur_x++; -// cur_y--; -// break; -// case SOUTH_WEST: -// cur_x--; -// cur_y--; -// break; -// case NORTH_WEST: -// cur_x--; -// cur_y++; -// break; -// } - - // Update the map based on the direction of the car (N, E, S, W) - // update_map(g_direction.orientation, cur_x, cur_y); - - // printf("Current Position: (%d, %d)\n", cur_x, cur_y); - // print_map(); - - // print_roll_and_pitch(g_direction.roll_angle, g_direction.pitch_angle); } void monitor_direction_task(void *pvParameters) { volatile direction_t *p_direction = NULL; - p_direction = (direction_t *) pvParameters; + p_direction = (direction_t *)pvParameters; for (;;) { @@ -379,7 +344,7 @@ magnetometer_tasks_init(car_struct_t *car_struct) xTaskCreate(monitor_direction_task, "Direction Task", configMINIMAL_STACK_SIZE, - (void *) car_struct->p_direction, + (void *)car_struct->p_direction, PRIO, &h_direction_task); } diff --git a/frtos/magnetometer/magnetometer_test.c b/frtos/magnetometer/magnetometer_test.c index 703bbbe..b22b69a 100644 --- a/frtos/magnetometer/magnetometer_test.c +++ b/frtos/magnetometer/magnetometer_test.c @@ -1,9 +1,8 @@ #include "magnetometer_init.h" #include "magnetometer_direction.h" -#include "map.h" -#define DIRECTION_TASK_PRIORITY (tskIDLE_PRIORITY + 1UL) +#define DIRECTION_TASK_PRIORITY (tskIDLE_PRIORITY + 1UL) void launch() @@ -21,31 +20,23 @@ launch() } int -main (void) +main(void) { stdio_usb_init(); direction_t direction; - car_struct_t car_struct = {.p_direction = &direction}; - - int grid_rows = 10; // Define the number of rows in your grid - int grid_cols = 10; // Define the number of columns in your grid - - car_path_grid = create_grid(grid_rows, grid_cols); + car_struct_t car_struct = { .p_direction = &direction }; sleep_ms(2000); printf("Test started!\n"); magnetometer_init(&car_struct); -// printf("Magnetometer initialized!\n"); - magnetometer_tasks_init(&car_struct); vTaskStartScheduler(); -// launch(); - return(0); + return (0); } \ No newline at end of file diff --git a/frtos/magnetometer/map.h b/frtos/magnetometer/map.h index 2737233..efe3c5d 100644 --- a/frtos/magnetometer/map.h +++ b/frtos/magnetometer/map.h @@ -9,29 +9,33 @@ #ifndef TEST_PROJECT_MAP_H #define TEST_PROJECT_MAP_H - // Define the grid structure -typedef struct { - bool **data; // 2D array to represent the grid - int rows; // Number of rows in the grid - int cols; // Number of columns in the grid +typedef struct +{ + bool **data; // 2D array to represent the grid + int rows; // Number of rows in the grid + int cols; // Number of columns in the grid } Grid; // Global grid to track the car's path Grid *car_path_grid; // Function to create and initialize a grid -Grid *create_grid(int rows, int cols) { - Grid *grid = (Grid *) malloc(sizeof(Grid)); +Grid * +create_grid(int rows, int cols) +{ + Grid *grid = (Grid *)malloc(sizeof(Grid)); grid->rows = rows; grid->cols = cols; // Allocate memory for the 2D array - grid->data = (bool **) malloc(rows * sizeof(bool *)); - for (int i = 0; i < rows; i++) { - grid->data[i] = (bool *) malloc(cols * sizeof(bool)); - for (int j = 0; j < cols; j++) { - grid->data[i][j] = false; // Initialize to 'false' (unvisited) + grid->data = (bool **)malloc(rows * sizeof(bool *)); + for (int i = 0; i < rows; i++) + { + grid->data[i] = (bool *)malloc(cols * sizeof(bool)); + for (int j = 0; j < cols; j++) + { + grid->data[i][j] = false; // Initialize to 'false' (unvisited) } } @@ -39,23 +43,32 @@ Grid *create_grid(int rows, int cols) { } // Function to mark a cell as visited -void mark_cell(Grid *grid, int row, int col) { - if (row >= 0 && row < grid->rows && col >= 0 && col < grid->cols) { +void +mark_cell(Grid *grid, int row, int col) +{ + if (row >= 0 && row < grid->rows && col >= 0 && col < grid->cols) + { grid->data[row][col] = true; } } // Function to check if a cell has been visited -bool is_cell_visited(Grid *grid, int row, int col) { - if (row >= 0 && row < grid->rows && col >= 0 && col < grid->cols) { +bool +is_cell_visited(Grid *grid, int row, int col) +{ + if (row >= 0 && row < grid->rows && col >= 0 && col < grid->cols) + { return grid->data[row][col]; } - return false; // Consider out-of-bounds as unvisited + return false; // Consider out-of-bounds as unvisited } // Function to destroy the grid and free memory -void destroy_grid(Grid *grid) { - for (int i = 0; i < grid->rows; i++) { +void +destroy_grid(Grid *grid) +{ + for (int i = 0; i < grid->rows; i++) + { free(grid->data[i]); } free(grid->data); @@ -64,12 +77,15 @@ void destroy_grid(Grid *grid) { // Function to update the map based on car's current orientation // Function to update the map based on car's current orientation and position -void update_map(int orientation, int cur_x, int cur_y) { +void +update_map(int orientation, int cur_x, int cur_y) +{ // Define offsets for different orientations int offset_x = 0; int offset_y = 0; - switch (orientation) { + switch (orientation) + { case NORTH: offset_y = 1; break; @@ -105,24 +121,27 @@ void update_map(int orientation, int cur_x, int cur_y) { mark_cell(car_path_grid, cur_x + offset_x, cur_y + offset_y); } - // Function to print the map -void print_map() { +void +print_map() +{ // Invert the map, 0,0 is at the Middle // Print 1 for visited cells and 0 for unvisited cells - for (int i = car_path_grid->rows - 1; i >= 0; i--) { - for (int j = 0; j < car_path_grid->cols; j++) { + for (int i = car_path_grid->rows - 1; i >= 0; i--) + { + for (int j = 0; j < car_path_grid->cols; j++) + { (car_path_grid->data[j][i]) ? printf("1 ") : printf("0 "); -// case false: -// printf("0 "); -// break; -// case true: -// printf("1 "); -// break; -// } + // case false: + // printf("0 "); + // break; + // case true: + // printf("1 "); + // break; + // } } printf("\n"); } } -#endif //TEST_PROJECT_MAP_H +#endif // TEST_PROJECT_MAP_H diff --git a/frtos/map/CMakeLists.txt b/frtos/map/CMakeLists.txt new file mode 100644 index 0000000..9402536 --- /dev/null +++ b/frtos/map/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable( + map_test + map_test.c +) + +target_link_libraries( + map_test + hardware_adc + pico_stdlib + pico_rand + FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap +) + +target_include_directories(map_test + PRIVATE ../config + ../car +) + +pico_enable_stdio_usb(map_test 1) +pico_add_extra_outputs(map_test) \ No newline at end of file diff --git a/frtos/map/map_test.c b/frtos/map/map_test.c new file mode 100644 index 0000000..7f2d9ea --- /dev/null +++ b/frtos/map/map_test.c @@ -0,0 +1,24 @@ + +#include "mapping.h" +#include "car_config.h" + +int +main(void) +{ + stdio_usb_init(); + maze_t maze; + + sleep_ms(3000); + + printf("Test started!\n"); + + mapping_init(&maze); + printf("Mapping initialized!\n"); + + mapping_tasks_init(&maze); + printf("Mapping tasks initialized!\n"); + + vTaskStartScheduler(); + + return (0); +} \ No newline at end of file diff --git a/frtos/map/mapping.h b/frtos/map/mapping.h new file mode 100644 index 0000000..de4c667 --- /dev/null +++ b/frtos/map/mapping.h @@ -0,0 +1,735 @@ +/** + * @file mapping.h + * @brief Map the environment using the line sensor and the ultrasonic sensor + * + * Reference: + * https://stackoverflow.com/questions/37207022/flood-fill-algorithm-maze + * + * @author Woon Jun Wei + */ + +#ifndef MAPPING_H +#define MAPPING_H + +#include +#include "pico/stdlib.h" +#include +#include "time.h" +#include "pico/rand.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "message_buffer.h" +#include "semphr.h" + +#include "car_config.h" + +// Function to generate a random number between min and max (inclusive) +int +generate_random(int min, int max) +{ + int num = (get_rand_32() % (max - min + 1)) + min; + printf("Random number generated: %d\n", num); + return num; +} + +// Define a queue structure for BFS +typedef struct +{ + int x; + int y; +} QueueNode; + +typedef struct +{ + QueueNode *array; + int front, rear, size; + unsigned capacity; +} Queue; + +// Function to create a new queue +Queue * +createQueue(unsigned capacity) +{ + Queue *queue = (Queue *)malloc(sizeof(Queue)); + queue->capacity = capacity; + queue->front = queue->size = 0; + queue->rear = capacity - 1; + queue->array = (QueueNode *)malloc(capacity * sizeof(QueueNode)); + return queue; +} + +// Function to check if the queue is empty +bool +isEmpty(Queue *queue) +{ + return (queue->size == 0); +} + +// Function to check if the queue is full +bool +isFull(Queue *queue) +{ + return (queue->size == queue->capacity); +} + +// Function to enqueue a cell in the queue +void +enqueue(Queue *queue, int x, int y) +{ + if (isFull(queue)) + return; + queue->rear = (queue->rear + 1) % queue->capacity; + queue->array[queue->rear].x = x; + queue->array[queue->rear].y = y; + queue->size = queue->size + 1; +} + +// Function to dequeue a cell from the queue +QueueNode +dequeue(Queue *queue) +{ + QueueNode cell = queue->array[queue->front]; + queue->front = (queue->front + 1) % queue->capacity; + queue->size = queue->size - 1; + return cell; +} + +// Function to perform BFS and find the shortest path +void +bfs_shortest_path(maze_t *maze, int startX, int startY) +{ + // Create a queue for BFS + Queue *queue = createQueue(maze->height * maze->width); + + // Initialize visited array + bool visited[maze->height][maze->width]; + for (int i = 0; i < maze->height; i++) + { + for (int j = 0; j < maze->width; j++) + { + visited[i][j] = false; + } + } + + // Mark the starting cell as visited and enqueue it + visited[startY][startX] = true; + enqueue(queue, startX, startY); + + // Define directions (up, down, left, right) + int dx[] = { -1, 1, 0, 0 }; + int dy[] = { 0, 0, -1, 1 }; + + // Perform BFS + while (!isEmpty(queue)) + { + // Dequeue a cell and process it + QueueNode current = dequeue(queue); + int x = current.x; + int y = current.y; + + // Process the cell (you can customize this part based on your needs) + // Here, we mark the cell with a special character to indicate it's part + // of the shortest path + maze->mazecells[y][x].type = 'P'; // 'P' for path + + // Explore adjacent cells + for (int i = 0; i < 4; i++) + { + int newX = x + dx[i]; + int newY = y + dy[i]; + + // Check if the new position is within the maze boundaries + if (newX >= 0 && newX < maze->width && newY >= 0 + && newY < maze->height) + { + // Check if the cell is not a wall and hasn't been visited + if (maze->mazecells[newY][newX].type != 'X' + && !visited[newY][newX]) + { + // Mark the new cell as visited and enqueue it + visited[newY][newX] = true; + enqueue(queue, newX, newY); + } + } + } + } + + // Free the allocated memory for the queue + free(queue); +} + +/** + * Create a map with hardcoded walls, obstacles, and the goal + * With the start point at the bottom left corner. + * Ensures there is at least one clear path from start to goal. + * @param maze + */ +void +create_map(maze_t *maze) +{ + // Create the map based on maze height and width + for (int i = 0; i < maze->height; i++) + { + for (int j = 0; j < maze->width; j++) + { + if (i == 0 || i == maze->height - 1 || j == 0 + || j == maze->width - 1) + { + maze->mazecells[i][j].type = 'X'; // Walls at the border + } + else + { + // Randomly place walls and obstacles + if (generate_random(0, 9) + < 2) // Adjust the threshold for more or fewer obstacles + { + maze->mazecells[i][j].type = 'X'; // Obstacle + } + else + { + maze->mazecells[i][j].type = ' '; // Empty space + } + } + maze->mazecells[i][j].reachable = 0; + maze->mazecells[i][j].visited = 0; + } + } + + // Set the start point + maze->mazecells[0][0].type = 'S'; + maze->mazecells[0][0].reachable = 1; + maze->mazecells[0][0].visited = 1; + + // Set the goal (assuming it's at the top-right corner) + maze->mazecells[maze->height - 1][maze->width - 1].type = 'G'; + + // Ensure there is a clear path from start to goal + for (int i = 1; i < maze->height - 1; i++) + { + maze->mazecells[i][maze->width / 2].type = ' '; // Clear path + } +} + +/** + * @brief Print the map + * @param maze + */ +void +print_map(maze_t *maze) +{ + for (int i = maze->height - 1; i >= 0; i--) + { + for (int j = 0; j < maze->width; j++) + { + char cellType = maze->mazecells[j][i].type; + + switch (cellType) + { + case 'X': + printf("X "); // Wall + break; + case 'O': + printf("O "); // Obstacle + break; + case 'S': + printf("S "); // Start + break; + case 'G': + printf("G "); // Goal + break; + case 'C': + printf("C "); // Car + break; + case 'V': + printf("V "); // Visited + break; + default: + printf(" "); // Empty space + break; + } + } + printf("\n"); + } +} + +/** + * Create a hardcoded map with a clear path from start to goal + * @param maze + */ +void +create_hardcoded_map(maze_t *maze) +{ + // Set fixed height and width during initialization + maze->height = 5; + maze->width = 5; + + // Create the map based on the image + char hardcoded_map[5][5] = { { ' ', ' ', ' ', ' ', ' ' }, + { 'S', 'X', 'X', 'X', ' ' }, + { ' ', 'X', ' ', ' ', ' ' }, + { ' ', 'X', ' ', 'X', ' ' }, + { ' ', ' ', ' ', 'X', 'G' } }; + + // Copy the hardcoded map to the maze structure + for (int i = 0; i < maze->height; i++) + { + for (int j = 0; j < maze->width; j++) + { + maze->mazecells[i][j].type = hardcoded_map[i][j]; + maze->mazecells[i][j].reachable = 0; + maze->mazecells[i][j].visited = 0; + } + } + + printf("Here is the hardcoded map:\n"); + print_map(maze); +} + +/** + * @brief Mapping Initialization + */ +void +mapping_init(maze_t *p_maze) +{ + printf("Initializing mapping\n"); + + // Set fixed height and width during initialization + p_maze->height = 5; + p_maze->width = 5; + + // Create the maze + printf("Creating maze\n"); + create_hardcoded_map(p_maze); + printf("Maze created\n"); +} + +/** + * @brief Print the map with the reachable cells + * @param maze + */ +void +print_map_reachable(maze_t *maze) +{ + for (int i = maze->height - 1; i >= 0; i--) + { + for (int j = 0; j < maze->width; j++) + { + printf("%d ", maze->mazecells[j][i].reachable); + } + printf("\n"); + } +} + +/** + * @brief Perform floodfill on the maze + * @param maze + * @param x starting position x-coordinate + * @param y starting position y-coordinate + * @param value value to fill + */ +void +floodfill(maze_t *maze, int x, int y, int value) +{ + // Check if the current position is within the maze boundaries and not + // visited + if (x >= 0 && x < maze->width && y >= 0 && y < maze->height + && maze->mazecells[x][y].visited == 0) + { + maze->mazecells[x][y].reachable = value; + maze->mazecells[x][y].visited = 1; + + // Recursive floodfill for neighboring cells + floodfill(maze, x + 1, y, value + 1); // right + floodfill(maze, x - 1, y, value + 1); // left + floodfill(maze, x, y + 1, value + 1); // up + floodfill(maze, x, y - 1, value + 1); // down + } +} + +/** + * @brief Function to check if the entire map is filled + * @param maze + * @return true if the entire map is filled, false otherwise + */ +bool +maze_explored(const maze_t *maze) +{ + for (int i = 0; i < maze->height; i++) + { + for (int j = 0; j < maze->width; j++) + { + if (maze->mazecells[j][i].type != 'X' + && maze->mazecells[j][i].type != 'V' + && maze->mazecells[j][i].type != 'C' + && maze->mazecells[j][i].type != 'G' + && maze->mazecells[j][i].type != 'S') + { + return false; + } + } + } + return true; +} + +// Update the find_shortest_path function with the newly created +// bfs_shortest_path function +void +find_shortest_path(maze_t *maze) +{ + // Assuming the starting point is the bottom-left corner (0, 0) + int startX = 0; + int startY = 0; + + // Perform BFS to find the shortest path + bfs_shortest_path(maze, startX, startY); +} + +/** + * @brief Function to backtrack to the start from the goal iteratively + * @param maze + * @param currentX pointer to the current X position + * @param currentY pointer to the current Y position + */ +void +backtrack_to_start(maze_t *maze, int *currentX, int *currentY) +{ + printf("Backtracking to the start...\n"); + + // Continue backtracking until reaching the start + while (*currentX != 0 || *currentY != 0) + { + printf("Backtracking...\n"); + print_map(maze); + + // Update the current cell as part of the backtracking path + maze->mazecells[*currentX][*currentY].type = 'P'; // 'P' for path + + // Move the car towards the starting point + if (*currentX > 0) + { + (*currentX)--; + } + else if (*currentY > 0) + { + (*currentY)--; + } + + // Print the map after updating the current cell during backtracking + printf("Map after updating current cell during backtracking:\n"); + print_map(maze); + + // Print the car's position in the map + printf("Car's position during backtracking: (%d, %d)\n", + *currentX, + *currentY); + + vTaskDelay( + pdMS_TO_TICKS(100)); // Delay to simulate time between movements + } + + printf("Backtracking completed. Reached the start!\n"); +} + +/** + * @brief Task to demonstrate the car following the shortest path from start to + * goal + * @param pvParameters + */ +void +demo_shortest_path_task(void *pvParameters) +{ + maze_t *maze = (maze_t *)pvParameters; + + // Assuming the starting point is the bottom-left corner (0, 0) + int currentX = 0; + int currentY = 0; + + // Find the shortest path using BFS + bfs_shortest_path(maze, currentX, currentY); + + printf("Shortest path found. Demonstrating the car's movement...\n"); + + // Iterate through the path and demonstrate the car's movement + for (int i = maze->height - 1; i >= 0; i--) + { + for (int j = 0; j < maze->width; j++) + { + if (maze->mazecells[i][j].type == 'P') + { + // Move the car to the cell in the shortest path + currentX = j; + currentY = i; + + // Print the map with the car's position + printf("Map with the car's position (BFS):\n"); + print_map(maze); + + // Delay to simulate the car's movement + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + } + + printf("Car reached the goal following the shortest path!\n"); + + vTaskDelete(NULL); // Delete the demonstration task +} + +/** + * @brief Task to perform mapping of the maze + * @param pvParameters + */ +void +mapping_task(void *pvParameters) +{ + maze_t *maze = (maze_t *)pvParameters; + int currentX = 0; // Initial X position + int currentY = 0; // Initial Y position + + // Reset maze before mapping + for (int i = 0; i < maze->height; i++) + { + for (int j = 0; j < maze->width; j++) + { + maze->mazecells[j][i].visited = 0; + } + } + + // Explore the maze and perform floodfill + for (;;) + { + // Simulate car movement (you can replace this logic with your actual + // movement algorithm) + + mapping_direction_t moveDirection + = (mapping_direction_t)(get_rand_32() % 4); + + // Update the previously visited position before moving + if (maze->mazecells[currentX][currentY].type != 'S' + && maze->mazecells[currentX][currentY].type != 'G') + { + maze->mazecells[currentX][currentY].type = 'V'; // 'V' for visited + } + + switch (moveDirection) + { + case up: + if (currentY < maze->height - 1 + && maze->mazecells[currentX][currentY + 1].type != 'X') + { + currentY++; + } + break; + case down: + if (currentY > 0 + && maze->mazecells[currentX][currentY - 1].type != 'X') + { + currentY--; + } + break; + case left: + if (currentX > 0 + && maze->mazecells[currentX - 1][currentY].type != 'X') + { + currentX--; + } + break; + case right: + if (currentX < maze->width - 1 + && maze->mazecells[currentX + 1][currentY].type != 'X') + { + currentX++; + } + break; + } + + // Update the car's position in the maze + if (maze->mazecells[currentX][currentY].type != 'S') + { + maze->mazecells[currentX][currentY].type = 'C'; // 'C' for car + } + + // Print the map with the car's position + printf("Map with the car's position:\n"); + print_map(maze); + + // Floodfill the maze after each movement + floodfill(maze, maze->width - 1, 0, 0); + + // Check if the car has explored the entire maze + printf("%d\n", maze_explored(maze)); + if (maze_explored(maze)) + { + printf("Entire maze explored!\n"); + + // Continue with backtracking, BFS, and demonstration of the + // shortest path + printf("Now Backtracking...\n"); + backtrack_to_start(maze, ¤tX, ¤tY); + + printf("Map after backtracking:\n"); + print_map(maze); + + // Find the shortest path after backtracking + printf("Finding the shortest path...\n"); + find_shortest_path(maze); + + // Create a task to demonstrate the shortest path + xTaskCreate(demo_shortest_path_task, + "demo_shortest_path_task", + configMINIMAL_STACK_SIZE, + (void *)maze, + PRIO, + NULL); + } + + vTaskDelay( + pdMS_TO_TICKS(100)); // Delay to simulate time between movements + } +} + +/** + * @brief Task to perform backtracking from the goal to the start + * @param pvParameters + */ +void +backtracking_task(void *pvParameters) +{ + maze_t *maze = (maze_t *)pvParameters; + + int currentX = 0; // Initial X position + int currentY = 0; // Initial Y position + + printf("Backtracking to the start...\n"); + backtrack_to_start(maze, ¤tX, ¤tY); + + printf("Map after backtracking:\n"); + print_map(maze); + + vTaskDelete(NULL); // Delete the backtracking task +} + +/** + * @brief Task to show the movement from start to goal + * @param pvParameters + */ +void +movement_task(void *pvParameters) +{ + maze_t *maze = (maze_t *)pvParameters; + + int currentX = 0; // Initial X position + int currentY = 0; // Initial Y position + + for (;;) + { + // Simulate car movement (you can replace this logic with your actual + // movement algorithm) + mapping_direction_t moveDirection + = (mapping_direction_t)(get_rand_32() % 4); + + // Update the previously visited position before moving + if (maze->mazecells[currentX][currentY].type != 'S' + && maze->mazecells[currentX][currentY].type != 'G') + { + maze->mazecells[currentX][currentY].type = 'V'; // 'V' for visited + } + + switch (moveDirection) + { + case up: + if (currentY < maze->height - 1 + && maze->mazecells[currentX][currentY + 1].type != 'X') + { + currentY++; + } + break; + case down: + if (currentY > 0 + && maze->mazecells[currentX][currentY - 1].type != 'X') + { + currentY--; + } + break; + case left: + if (currentX > 0 + && maze->mazecells[currentX - 1][currentY].type != 'X') + { + currentX--; + } + break; + case right: + if (currentX < maze->width - 1 + && maze->mazecells[currentX + 1][currentY].type != 'X') + { + currentX++; + } + break; + } + + // Update the car's position in the maze + if (maze->mazecells[currentX][currentY].type != 'S') + { + maze->mazecells[currentX][currentY].type = 'C'; // 'C' for car + } + + // Print the map with the car's position + printf("Map with the car's position:\n"); + print_map(maze); + + vTaskDelay( + pdMS_TO_TICKS(100)); // Delay to simulate time between movements + } +} + +/** + * @brief Initialise tasks for the Maze + * @param maze + */ +void +mapping_tasks_init(maze_t *maze) +{ + // Task handles + TaskHandle_t mapping_task_handle = NULL; + TaskHandle_t backtracking_task_handle = NULL; + TaskHandle_t demo_shortest_path_handle = NULL; + TaskHandle_t movement_task_handle = NULL; + + // Create tasks + xTaskCreate(mapping_task, + "mapping_task", + configMINIMAL_STACK_SIZE, + (void *)maze, + PRIO, + &mapping_task_handle); + + xTaskCreate(backtracking_task, + "backtracking_task", + configMINIMAL_STACK_SIZE, + (void *)maze, + PRIO, + &backtracking_task_handle); + + // Shortest path task demo + xTaskCreate(demo_shortest_path_task, + "demo_shortest_path_task", + configMINIMAL_STACK_SIZE, + (void *)maze, + PRIO, + &demo_shortest_path_handle); + + xTaskCreate(movement_task, + "movement_task", + configMINIMAL_STACK_SIZE, + (void *)maze, + PRIO, + &movement_task_handle); + + // Suspend all tasks except the mapping task + vTaskSuspend(backtracking_task_handle); + vTaskSuspend(demo_shortest_path_handle); + vTaskSuspend(movement_task_handle); +} + +#endif /* MAPPING_H */ diff --git a/frtos/motor/motor_direction.h b/frtos/motor/motor_direction.h index 3cb2e65..2948ae6 100644 --- a/frtos/motor/motor_direction.h +++ b/frtos/motor/motor_direction.h @@ -49,7 +49,8 @@ revert_wheel_direction() * @param range acceptable range * @return true if the current direction is within the range of the target */ -bool check_direction(float current_direction, float target_direction, float range) +bool +check_direction(float current_direction, float target_direction, float range) { // Normalize directions to be within 0 to 360 degrees current_direction = fmod(current_direction, 360.0f); diff --git a/frtos/motor/motor_pid.h b/frtos/motor/motor_pid.h index 277dcf0..24d3b77 100644 --- a/frtos/motor/motor_pid.h +++ b/frtos/motor/motor_pid.h @@ -26,17 +26,15 @@ compute_pid(float *integral, float *prev_error, car_struct_t *car_struct) float derivative = error - *prev_error; - float control_signal - = car_struct->p_pid->kp_value * error - + car_struct->p_pid->ki_value * (*integral) - + car_struct->p_pid->kd_value * derivative; + float control_signal = car_struct->p_pid->kp_value * error + + car_struct->p_pid->ki_value * (*integral) + + car_struct->p_pid->kd_value * derivative; *prev_error = error; return control_signal; } - /*! * @brief Repeating timer handler for the PID controller * @param ppp_timer The repeating timer diff --git a/frtos/motor/motor_speed.h b/frtos/motor/motor_speed.h index 43ceb0e..4bb3774 100644 --- a/frtos/motor/motor_speed.h +++ b/frtos/motor/motor_speed.h @@ -22,8 +22,7 @@ h_wheel_sensor_isr_handler(void) gpio_acknowledge_irq(SPEED_PIN_LEFT, GPIO_IRQ_EDGE_FALL); BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(g_left_sem, - &xHigherPriorityTaskWoken); + xSemaphoreGiveFromISR(g_left_sem, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } @@ -32,13 +31,11 @@ h_wheel_sensor_isr_handler(void) gpio_acknowledge_irq(SPEED_PIN_RIGHT, GPIO_IRQ_EDGE_FALL); BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(g_right_sem, - &xHigherPriorityTaskWoken); + xSemaphoreGiveFromISR(g_right_sem, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } - /*! * @brief Task to monitor and control the speed of the wheel * @param ppp_motor motor_speed_t struct pointer @@ -56,15 +53,15 @@ monitor_wheel_speed_task(void *ppp_motor) for (;;) { - if ((xSemaphoreTake(*p_motor->p_sem, pdMS_TO_TICKS(100)) - == pdTRUE) && (*p_motor->use_pid == true)) + if ((xSemaphoreTake(*p_motor->p_sem, pdMS_TO_TICKS(100)) == pdTRUE) + && (*p_motor->use_pid == true)) { curr_time = time_us_64(); elapsed_time = curr_time - prev_time; prev_time = curr_time; p_motor->speed.current_cms - = (float) (SLOT_DISTANCE_CM_MODIFIED / elapsed_time); + = (float)(SLOT_DISTANCE_CM_MODIFIED / elapsed_time); p_motor->speed.distance_cm += SLOT_DISTANCE_CM; } @@ -112,13 +109,14 @@ set_wheel_speed_synced(uint32_t pwm_level, car_struct_t *pp_car_strut) * @param distance_cm distance to travel in cm */ void -distance_to_stop(car_struct_t * pp_car_struct, float distance_cm) +distance_to_stop(car_struct_t *pp_car_struct, float distance_cm) { float initial = pp_car_struct->p_left_motor->speed.distance_cm; for (;;) { - if (pp_car_struct->p_left_motor->speed.distance_cm - initial >= distance_cm) + if (pp_car_struct->p_left_motor->speed.distance_cm - initial + >= distance_cm) { set_wheel_direction(DIRECTION_MASK); set_wheel_speed_synced(0u, pp_car_struct); @@ -127,8 +125,8 @@ distance_to_stop(car_struct_t * pp_car_struct, float distance_cm) vTaskDelay(pdMS_TO_TICKS(10)); } vTaskDelay(pdMS_TO_TICKS(1000)); - pp_car_struct->p_right_motor->speed.distance_cm = - pp_car_struct->p_left_motor->speed.distance_cm; + pp_car_struct->p_right_motor->speed.distance_cm + = pp_car_struct->p_left_motor->speed.distance_cm; } #endif /* MOTOR_SPEED_H */ diff --git a/frtos/motor/motor_test.c b/frtos/motor/motor_test.c index b5182e4..fc2eb91 100644 --- a/frtos/motor/motor_test.c +++ b/frtos/motor/motor_test.c @@ -19,8 +19,6 @@ motor_control_task(void *params) } } - - int main(void) { diff --git a/frtos/rtos_car.c b/frtos/rtos_car.c index bb70bf9..377b313 100644 --- a/frtos/rtos_car.c +++ b/frtos/rtos_car.c @@ -3,6 +3,7 @@ #include "ultrasonic_sensor.h" #include "car_config.h" #include "motor_init.h" +#include "frontend.h" /*! * @brief Check if the car is on the line @@ -18,6 +19,11 @@ check_line_touch(void *params) | (car_struct->obs->right_sensor_detected); } +/** + * @brief Check if the car is on the line or if there is an obstacle + * @param params The car_struct + * @return 1 if there is an obstacle, 0 otherwise + */ bool check_collision(void *params) { @@ -25,14 +31,21 @@ check_collision(void *params) // return ((car_struct->obs->left_sensor_detected << 1) // | (car_struct->obs->right_sensor_detected)) // || car_struct->obs->ultrasonic_detected; - return check_line_touch(car_struct) || - car_struct->obs->ultrasonic_detected; + return check_line_touch(car_struct) || car_struct->obs->ultrasonic_detected; } +/*! + * @brief Task for going straight, shift left or right when there is a line, + * by changing the distance of the right wheel, so the speed of the wheel + * will be changed by the PID function. + * @param p_car_struct + */ void -motor_control_task(void *params) +motor_control_task(void *p_car_struct) { - car_struct_t *car_struct = (car_struct_t *)params; + car_struct_t *car_struct = (car_struct_t *)p_car_struct; + set_wheel_direction(DIRECTION_FORWARD); + set_wheel_speed_synced(90u, car_struct); for (;;) { @@ -40,24 +53,19 @@ motor_control_task(void *params) switch (temp) { default: - set_wheel_direction(DIRECTION_FORWARD); - set_wheel_speed_synced(90u, car_struct); - distance_to_stop(car_struct, 50.f); - vTaskDelay(pdMS_TO_TICKS(3000)); break; case 0b01: - car_struct->p_right_motor->speed.current_cms - += SLOT_DISTANCE_CM * 1000.f; + car_struct->p_right_motor->speed.distance_cm + -= SLOT_DISTANCE_CM; break; case 0b10: - car_struct->p_right_motor->speed.current_cms - -= SLOT_DISTANCE_CM * 1000.f; + car_struct->p_right_motor->speed.distance_cm + += SLOT_DISTANCE_CM; break; case 0b11: - // set_wheel_direction(DIRECTION_MASK); - // set_wheel_speed_synced(0u, car_struct); - // vTaskDelay(pdMS_TO_TICKS(1000)); - // turn(DIRECTION_LEFT, 90u, 90u, car_struct); + turn(DIRECTION_LEFT, 90u, 90u, car_struct); + set_wheel_direction(DIRECTION_FORWARD); + set_wheel_speed_synced(90u, car_struct); break; } @@ -65,6 +73,11 @@ motor_control_task(void *params) } } +/*! + * @brief Go forward until there is an obstacle, reverse the wheel direction, + * travel a certain distance, then stop. + * @param p_car_struct + */ void obs_task(void *params) { @@ -77,35 +90,39 @@ obs_task(void *params) { if (car_struct->obs->ultrasonic_detected) { -// turn(DIRECTION_LEFT, 130u, 90u, car_struct); -// set_wheel_direction(DIRECTION_FORWARD); -// set_wheel_speed_synced(90u, car_struct); -// + // turn(DIRECTION_LEFT, 130u, 90u, car_struct); + // set_wheel_direction(DIRECTION_FORWARD); + // set_wheel_speed_synced(90u, car_struct); + // revert_wheel_direction(); - distance_to_stop(car_struct, 100.f); + distance_to_stop(car_struct, 20.f); } vTaskDelay(pdMS_TO_TICKS(50)); } } +/*! + * @brief Task to demonstrate the turning function using magnetometer + * @param params + */ void turn_task(void *params) { - car_struct_t *car_struct = (car_struct_t *)params; + car_struct_t *car_struct = (car_struct_t *)params; - for (;;) - { - set_wheel_direction(DIRECTION_FORWARD); - set_wheel_speed_synced(90u, car_struct); + for (;;) + { + set_wheel_direction(DIRECTION_FORWARD); + set_wheel_speed_synced(89u, car_struct); - distance_to_stop(car_struct, 50.f); - vTaskDelay(pdMS_TO_TICKS(1000)); + distance_to_stop(car_struct, 20.f); + vTaskDelay(pdMS_TO_TICKS(1000)); -// turn_to_yaw(DIRECTION_LEFT, 230.f, 80u, car_struct); + // turn_to_yaw(DIRECTION_LEFT, 230.f, 80u, car_struct); - turn(DIRECTION_RIGHT, 50.f, 90u, car_struct); - vTaskDelay(pdMS_TO_TICKS(1000)); - } + turn(DIRECTION_RIGHT, 80.f, 90u, car_struct); + vTaskDelay(pdMS_TO_TICKS(1000)); + } } void @@ -149,13 +166,16 @@ main(void) .obs = &obs, .p_direction = &direction }; + // webserver frontend + webserver_init(&car_struct); + // Magnetometer magnetometer_init(&car_struct); -// magnetometer_tasks_init(&car_struct); + // magnetometer_tasks_init(&car_struct); // updateDirection(car_struct.p_direction); printf("Magnetometer initialized!\n"); -// sleep_ms(1000); + // sleep_ms(1000); // ultra ultrasonic_init(&car_struct); @@ -175,31 +195,31 @@ main(void) sleep_ms(1000u); // control task - // TaskHandle_t h_motor_turning_task_handle = NULL; - // xTaskCreate(motor_control_task, - // "motor_turning_task", - // configMINIMAL_STACK_SIZE, - // (void *)&car_struct, - // PRIO, - // &h_motor_turning_task_handle); + // TaskHandle_t h_motor_turning_task_handle = NULL; + // xTaskCreate(motor_control_task, + // "motor_turning_task", + // configMINIMAL_STACK_SIZE, + // (void *)&car_struct, + // PRIO, + // &h_motor_turning_task_handle); // obs task -// TaskHandle_t h_obs_task_handle = NULL; -// xTaskCreate(obs_task, -// "obs_task", -// configMINIMAL_STACK_SIZE, -// (void *)&car_struct, -// PRIO, -// &h_obs_task_handle); + // TaskHandle_t h_obs_task_handle = NULL; + // xTaskCreate(obs_task, + // "obs_task", + // configMINIMAL_STACK_SIZE, + // (void *)&car_struct, + // PRIO, + // &h_obs_task_handle); // turn task - TaskHandle_t h_turn_task_handle = NULL; - xTaskCreate(turn_task, - "turn_task", - configMINIMAL_STACK_SIZE, - (void *)&car_struct, - PRIO, - &h_turn_task_handle); + TaskHandle_t h_turn_task_handle = NULL; + xTaskCreate(turn_task, + "turn_task", + configMINIMAL_STACK_SIZE, + (void *)&car_struct, + PRIO, + &h_turn_task_handle); // PID timer struct repeating_timer pid_timer; diff --git a/frtos/rtos_car_line.c b/frtos/rtos_car_line.c deleted file mode 100644 index f453482..0000000 --- a/frtos/rtos_car_line.c +++ /dev/null @@ -1,126 +0,0 @@ - -#include "line_sensor_init.h" -#include "ultrasonic_sensor.h" -#include "car_config.h" -#include "motor_init.h" - -/*! - * @brief Check if the car is on the line - * @param params - * @return - */ -uint8_t -check_line_touch(void *params) -{ - car_struct_t *car_struct = (car_struct_t *)params; - - return (car_struct->obs->left_sensor_detected << 1) - | (car_struct->obs->right_sensor_detected); -} - -void -motor_control_task(void *params) -{ - car_struct_t *car_struct = (car_struct_t *)params; - - set_wheel_direction(DIRECTION_FORWARD); - set_wheel_speed_synced(90u, car_struct); - - for (;;) - { - uint8_t temp = check_line_touch(car_struct); - switch (temp) - { - default: - break; - case 0b01: // right - car_struct->p_right_motor->speed.distance_cm -= - SLOT_DISTANCE_CM; - break; - case 0b10: //left - car_struct->p_right_motor->speed.distance_cm += - SLOT_DISTANCE_CM; - break; - case 0b11: - revert_wheel_direction(); - distance_to_stop(car_struct, 17.f); - turn(DIRECTION_LEFT, 90u, 90u, car_struct); - set_wheel_direction(DIRECTION_FORWARD); - set_wheel_speed_synced(90u, car_struct); - break; - } - - vTaskDelay(pdMS_TO_TICKS(50)); - } -} - -void -h_main_irq_handler(void) -{ - if (gpio_get_irq_event_mask(SPEED_PIN_LEFT) & GPIO_IRQ_EDGE_FALL) - { - gpio_acknowledge_irq(SPEED_PIN_LEFT, GPIO_IRQ_EDGE_FALL); - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(g_left_sem, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } - - if (gpio_get_irq_event_mask(SPEED_PIN_RIGHT) & GPIO_IRQ_EDGE_FALL) - { - gpio_acknowledge_irq(SPEED_PIN_RIGHT, GPIO_IRQ_EDGE_FALL); - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(g_right_sem, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } -} - -int -main(void) -{ - stdio_usb_init(); - - obs_t obs; - - motor_t motor_right; - motor_t motor_left; - motor_pid_t pid; - - direction_t direction; - - car_struct_t car_struct = { .p_right_motor = &motor_right, - .p_left_motor = &motor_left, - .p_pid = &pid, - .obs = &obs, - .p_direction = &direction }; - - // line - line_sensor_init(&car_struct); - line_tasks_init(&car_struct); - printf("Line sensor initialized!\n"); - - // motor - motor_init(&car_struct); - motor_tasks_init(&car_struct, &h_main_irq_handler); - printf("Motor initialized!\n"); - - sleep_ms(1000u); - - // control task - TaskHandle_t h_motor_turning_task_handle = NULL; - xTaskCreate(motor_control_task, - "motor_turning_task", - configMINIMAL_STACK_SIZE, - (void *)&car_struct, - PRIO, - &h_motor_turning_task_handle); - - // PID timer - struct repeating_timer pid_timer; - add_repeating_timer_ms(-50, repeating_pid_handler, &car_struct, &pid_timer); - - vTaskStartScheduler(); - - return (0); -} diff --git a/frtos/ultrasonic_sensor/CMakeLists.txt b/frtos/ultrasonic_sensor/CMakeLists.txt index 30fe799..64aa5b7 100644 --- a/frtos/ultrasonic_sensor/CMakeLists.txt +++ b/frtos/ultrasonic_sensor/CMakeLists.txt @@ -1,7 +1,7 @@ add_executable( ultrasonic_sensor ultrasonic_sensor.c - ) +) target_link_libraries( ultrasonic_sensor @@ -12,12 +12,12 @@ target_link_libraries( hardware_i2c FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap hardware_pwm - ) +) -target_include_directories(ultrasonic_sensor +target_include_directories(ultrasonic_sensor PRIVATE ../config ../motor - ) +) pico_enable_stdio_usb(ultrasonic_sensor 1) pico_add_extra_outputs(ultrasonic_sensor) \ No newline at end of file diff --git a/frtos/ultrasonic_sensor/ultrasonic_init.h b/frtos/ultrasonic_sensor/ultrasonic_init.h index eaf9488..b8ab816 100644 --- a/frtos/ultrasonic_sensor/ultrasonic_init.h +++ b/frtos/ultrasonic_sensor/ultrasonic_init.h @@ -35,7 +35,7 @@ check_obstacle(void *pvParameters) // Calculate the distance (in centimeters) uint32_t pulse_duration = end_time - start_time; float distance - = (pulse_duration * 0.034 / 2); // Speed of sound in cm/us + = (pulse_duration * 0.034 / 2); // Speed of sound in air to cm/us // printf("Distance: %.2f cm\n", distance); @@ -50,6 +50,11 @@ check_obstacle(void *pvParameters) } } + +/** + * @brief Initialise the Ultrasonic Sensor + * @details Initialise the Ultrasonic Sensor Pins and set default collision value + */ void ultrasonic_init(car_struct_t *car_struct) { diff --git a/frtos/ultrasonic_sensor/ultrasonic_sensor.c b/frtos/ultrasonic_sensor/ultrasonic_sensor.c index 9dc40ae..bcdf17e 100644 --- a/frtos/ultrasonic_sensor/ultrasonic_sensor.c +++ b/frtos/ultrasonic_sensor/ultrasonic_sensor.c @@ -6,7 +6,6 @@ #include "ultrasonic_sensor.h" #include "car_config.h" - int main(void) {