Merge branch 'main' into main

This commit is contained in:
Richie Wang 2023-11-28 14:54:51 +08:00 committed by GitHub
commit c7359d443f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1443 additions and 735 deletions

Binary file not shown.

View File

@ -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.

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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();

75
frtos/frontend/frontend.h Normal file
View File

@ -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);
}

View File

@ -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 @@
<div class="pid-output">
<p>PID Controller Output: <!--#pid--></p>
</div>
<div class="obstacle-output">
<p>Obstacle: <!--#obstacle--></p>
</div>
<div class="orientation-output">
<p>Orientation: <!--#orient--></p>
</div>

View File

@ -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 }};

Binary file not shown.

View File

@ -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
@ -26,14 +36,31 @@ u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) {
case 2: //PID
{
// whatever to display for PID
const char* PID = "54" ;
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);

View File

@ -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 */

View File

@ -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);

View File

@ -1,100 +0,0 @@
#include <stdio.h>
#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");
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

20
frtos/map/CMakeLists.txt Normal file
View File

@ -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)

24
frtos/map/map_test.c Normal file
View File

@ -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);
}

735
frtos/map/mapping.h Normal file
View File

@ -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 <stdio.h>
#include "pico/stdlib.h"
#include <stdlib.h>
#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, &currentX, &currentY);
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, &currentX, &currentY);
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 */

View File

@ -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);

View File

@ -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

View File

@ -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 */

View File

@ -19,8 +19,6 @@ motor_control_task(void *params)
}
}
int
main(void)
{

View File

@ -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;

View File

@ -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);
}

View File

@ -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
PRIVATE ../config
../motor
)
)
pico_enable_stdio_usb(ultrasonic_sensor 1)
pico_add_extra_outputs(ultrasonic_sensor)

View File

@ -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)
{

View File

@ -6,7 +6,6 @@
#include "ultrasonic_sensor.h"
#include "car_config.h"
int
main(void)
{