diff --git a/common/app/pixy_helper.c b/common/app/pixy_helper.c new file mode 100644 index 0000000..d137d71 --- /dev/null +++ b/common/app/pixy_helper.c @@ -0,0 +1,241 @@ +#include "pixy_helper.h" +#include "pixy.h" +#include "tft.h" +#include + + + +static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame); +static int saveBA81(FILE_HANDLE* handle, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame); + + +int pixy_render_full_frame(uint16_t x, uint16_t y) { + return pixy_render_cropped_frame(x,y,0,0,320,200); +} + + +int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) { + uint8_t* videodata; + int32_t response; + int32_t fourccc; + int8_t renderflags; + uint16_t xwidth; + uint16_t ywidth; + uint32_t size; + + + int return_value = pixy_command("cam_getFrame", // String id for remote procedure + INT8(0x21), // mode + INT16(xoffset), // xoffset + INT16(yoffset), // yoffset + INT16(width), // width + INT16(height), // height + END_OUT_ARGS, // separator + &response, // pointer to mem address for return value + &fourccc, + &renderflags, + &xwidth, + &ywidth, + &size, + &videodata, // pointer to mem address for returned frame + END_IN_ARGS); + + if(return_value==0) { + return_value = renderBA81(x,y,xwidth,ywidth,size,videodata); + } + + return return_value; +} + +int pixy_save_full_frame(FILE_HANDLE* handle) { + return pixy_save_cropped_frame(handle,0,0,320,200); +} + +int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) { + uint8_t* videodata; + int32_t response; + int32_t fourccc; + int8_t renderflags; + uint16_t xwidth; + uint16_t ywidth; + uint32_t size; + + + int return_value = pixy_command("cam_getFrame", // String id for remote procedure + INT8(0x21), // mode + INT16(xoffset), // xoffset + INT16(yoffset), // yoffset + INT16(width), // width + INT16(height), // height + END_OUT_ARGS, // separator + &response, // pointer to mem address for return value + &fourccc, + &renderflags, + &xwidth, + &ywidth, + &size, + &videodata, // pointer to mem address for returned frame + END_IN_ARGS); + + if(return_value==0) { + return_value = saveBA81(handle,xwidth,ywidth,size,videodata); + } + + return return_value; +} + + + + + +static void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b) +{ + if (y&1) + { + if (x&1) + { + *r = *pixel; + *g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2; + *b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2; + } + else + { + *r = (*(pixel-1)+*(pixel+1))>>1; + *g = *pixel; + *b = (*(pixel-width)+*(pixel+width))>>1; + } + } + else + { + if (x&1) + { + *r = (*(pixel-width)+*(pixel+width))>>1; + *g = *pixel; + *b = (*(pixel-1)+*(pixel+1))>>1; + } + else + { + *r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2; + *g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2; + *b = *pixel; + } + } + +} + + + + + + +static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame) +{ + uint16_t x, y; + uint8_t r, g, b; + + + // skip first line + frame += width; + + // don't render top and bottom rows, and left and rightmost columns because of color + // interpolation + //uint32_t decodedimage[(width-2)*(height-2)]; + uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2)); + + if(decodedimage==NULL) { //not enough free space to decode image in memory + //decode & render image pixel by pixel + for (y=1; yfpos; + uint32_t row_size_padded = ((width-2)*3 + 3) & (~3); //row size aligned to 4 bytes + uint32_t fpos_end = fpos + row_size_padded* (height-2); + + + // skip first line + frame += width; + + // don't render top and bottom rows, and left and rightmost columns because of color + // interpolation + + for (y=1; y +#include +#include "filesystem.h" + +/** + * Receives a fullsized frame from pixy and display's it on the display with the topleft corner at (x,y) + * @param x The x-Coordinate of the top left corner + * @param y The y-Coordinate of the top left corner + * @return 0 on success, otherwise the errorcode from pixy + */ +int pixy_render_full_frame(uint16_t x, uint16_t y); + +/** + * Receives a cropped frame from pixy and display's it on the display with the topleft corner at (x,y) + * @param x The x-Coordinate of the top left corner to draw the image + * @param y The y-Coordinate of the top left corner to draw the image + * @param xoffset The x-Coordinate on the pixy image from where on you want the frame data + * @param yoffset The y-Coordinate on the pixy image from where on you want the frame data + * @param width The width of the image recorded from pixy + * @param height The height of the image recorded from pixy + * @return 0 on success, otherwise the errorcode from pixy + */ +int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height); + +/** + * Receives a fullsized frame from pixy and saves it to the given file in the 24bit (b,g,a) format. + * Use this method to write the bitmap-data part of a windows bitmap (.bmp). + * This method will neither open nor close the passed file. + * @param handle The file to write the data to. The file must be open and it should be seeked to the right position. + * @return 0 on success, otherwise the errorcode from pixy + */ +int pixy_save_full_frame(FILE_HANDLE* handle); + +/** + * Receives a cropped frame from pixy and saves it to the given file in the 24bit (b,g,a) format. + * @param handle The file to write the data to. The file must be open and it should be seeked to the right position. + * @param xoffset The x-Coordinate on the pixy image from where on you want the frame data + * @param yoffset The y-Coordinate on the pixy image from where on you want the frame data + * @param width The width of the image recorded from pixy + * @param height The height of the image recorded from pixy + * @return 0 on success, otherwise the errorcode from pixy + */ +int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height); + +/** + * Sets the color signature to the color in the selected region of the frame + * @param signum the color signature number (1..7) + * @param xoffset The x-Coordinate of the topleft point of the region + * @param yoffset The y-Coordinate of the topleft point of the region + * @param width The width of the region + * @param height The height of the region + * @return 0 on success, otherwise the errorcode from pixy + */ +int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height); + +#endif /* PIXY_HELPER_H */ diff --git a/common/app/screen_filetest.c b/common/app/screen_filetest.c index f9aa9a0..fbc09ce 100644 --- a/common/app/screen_filetest.c +++ b/common/app/screen_filetest.c @@ -70,6 +70,7 @@ static void enter(void* screen) { y+=14; } + filesystem_dir_close(dir); y+=14; @@ -129,46 +130,10 @@ SCREEN_STRUCT* get_screen_filetest() { } static void image_test() { - //Source: http://stackoverflow.com/a/17040962/2606757 - FILE_HANDLE* file = filesystem_file_open("cpu.bmp"); - if(file==NULL) { + + if(!tft_draw_bitmap_file_unscaled(250,170,"cpu.bmp")) { tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not open cpu.bmp"); - return; } - - - unsigned char info[54]; - if(filesystem_file_read(file,info,54)!=F_OK) { - tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not read header of cpu.bmp"); - filesystem_file_close(file); - return; - } - - // extract image height and width from header - int width = *(int*)&info[18]; - int height = *(int*)&info[22]; - - filesystem_file_seek(file,*(int*)&info[10]); - - unsigned char data [width*4]; - tft_draw_rectangle(100-1,160-1,100-1+width,160-1+height,BLACK); - - for(int i = 0; i < height; i++) - { - filesystem_file_read(file,data,width*4); - for(int j = 0; j < width*4; j += 4) - { - unsigned char a = data[j]; - unsigned char r = data[j+1]; - unsigned char g = data[j+2]; - unsigned char b = data[j+3]; - if(a!=0) { - tft_draw_pixel(100+j/4,160+height-1-i,RGB(r,g,b)); - } - } - } - - filesystem_file_close(file); - + tft_draw_rectangle(250-1,170-1,250-1+64,170-1+64,BLACK); } diff --git a/common/app/screen_filetest.h b/common/app/screen_filetest.h index 3697153..eec7dbf 100644 --- a/common/app/screen_filetest.h +++ b/common/app/screen_filetest.h @@ -6,7 +6,7 @@ /*@{*/ /** - * @defgroup filetest Filetest + * @defgroup filetest Filetest (Screen) * The File-Test Screen tests the filesystem module. It read/writes from/to files and shows a bitmap */ /*@{*/ @@ -19,4 +19,5 @@ */ SCREEN_STRUCT* get_screen_filetest(); -/*@}@}*/ +/*@}*/ +/*@}*/ diff --git a/common/app/screen_guitest.h b/common/app/screen_guitest.h index abc72f9..82a9d49 100644 --- a/common/app/screen_guitest.h +++ b/common/app/screen_guitest.h @@ -7,7 +7,7 @@ /*@{*/ /** - * @defgroup guitest Guitest + * @defgroup guitest Guitest (Screen) * The Gui-Test Screen tests the gui and the tft module. */ /*@{*/ @@ -20,4 +20,5 @@ */ SCREEN_STRUCT* get_screen_guitest(); -/*@}@}*/ +/*@}*/ +/*@}*/ diff --git a/common/app/screen_main.c b/common/app/screen_main.c index 6baebfe..1ff52d6 100644 --- a/common/app/screen_main.c +++ b/common/app/screen_main.c @@ -2,13 +2,35 @@ #include "screen_guitest.h" #include "screen_pixytest.h" #include "screen_filetest.h" +#include "screen_photomode.h" +#include "screen_tracking.h" #include "button.h" #include "tft.h" +#include "filesystem.h" BUTTON_STRUCT b_guitest; BUTTON_STRUCT b_pixytest; BUTTON_STRUCT b_filetest; +BUTTON_STRUCT b_our_tracking; +BUTTON_STRUCT b_ref_tracking; +BUTTON_STRUCT b_photo_mode; + + +static void b_our_tracking_cb(void* button) { + tracking_set_mode(OUR_TRACKING); + gui_screen_navigate(get_screen_tracking()); +} + +static void b_ref_tracking_cb(void* button) { + tracking_set_mode(REFERENCE_TRACKING); + gui_screen_navigate(get_screen_tracking()); +} + +static void b_photo_mode_cb(void* button) { + gui_screen_navigate(get_screen_photomode()); +} + static void b_guitest_cb(void* button) { gui_screen_navigate(get_screen_guitest()); } @@ -25,47 +47,108 @@ static void b_pixytest_cb(void* button) { static void enter(void* screen) { tft_clear(WHITE); - //button to reach guitest - b_guitest.base.x1=25; //Start X of Button - b_guitest.base.y1=45; //Start Y of Button - b_guitest.base.x2=AUTO; //b_guitest.base.x1+160; //Auto Calculate X2 with String Width - b_guitest.base.y2=AUTO; //Auto Calculate Y2 with String Height - b_guitest.txtcolor=WHITE; //Set foreground color - b_guitest.bgcolor=HEX(0xDE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) - b_guitest.font=0; //Select Font - b_guitest.text="Gui/Tft Test"; //Set Text (For formatted strings take sprintf) - b_guitest.callback=b_guitest_cb; //Call b_guitest_cb as Callback - gui_button_add(&b_guitest); //Register Button (and run the callback from now on) + //Heading + tft_print_line(10,10,BLUE,TRANSPARENT,1,"Discoverpixy"); + tft_draw_line(0,40,319,40,BLACK); - //button to reach pixy test - b_pixytest.base.x1=150; //Start X of Button - b_pixytest.base.y1=45; //Start Y of Button - b_pixytest.base.x2=AUTO; //b_pixytest.base.x1+160; //Auto Calculate X2 with String Width - b_pixytest.base.y2=AUTO; //Auto Calculate Y2 with String Height - b_pixytest.txtcolor=BLUE; //Set foreground color - b_pixytest.bgcolor=HEX(0x10DE10); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) - b_pixytest.font=0; //Select Font - b_pixytest.text="Pixy Test"; //Set Text (For formatted strings take sprintf) - b_pixytest.callback=b_pixytest_cb; //Call b_pixytest_cb as Callback - gui_button_add(&b_pixytest); //Register Button (and run the callback from now on) + #define X_TAB 90 + #define BUTTON_SPACING 7 - //button to reach filesystem test - b_filetest.base.x1=240; //Start X of Button - b_filetest.base.y1=45; //Start Y of Button - b_filetest.base.x2=AUTO; //b_filetest.base.x1+160; //Auto Calculate X2 with String Width - b_filetest.base.y2=AUTO; //Auto Calculate Y2 with String Height - b_filetest.txtcolor=WHITE; //Set foreground color - b_filetest.bgcolor=HEX(0x501EA0); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) - b_filetest.font=0; //Select Font - b_filetest.text="File Test"; //Set Text (For formatted strings take sprintf) - b_filetest.callback=b_filetest_cb; //Call b_filetest_cb as Callback - gui_button_add(&b_filetest); //Register Button (and run the callback from now on) + //First line of buttons + #define Y_FIRST 60 + tft_print_line(10,Y_FIRST,BLACK,TRANSPARENT,0,"Tracking:"); + + b_our_tracking.base.x1=X_TAB; //Start X of Button + b_our_tracking.base.y1=Y_FIRST-3; //Start Y of Button + b_our_tracking.base.x2=AUTO; //Auto Calculate X2 with String Width + b_our_tracking.base.y2=AUTO; //Auto Calculate Y2 with String Height + b_our_tracking.txtcolor=WHITE; //Set foreground color + b_our_tracking.bgcolor=HEX(0xE30535); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) + b_our_tracking.font=0; //Select Font + b_our_tracking.text="Our Tracking"; //Set Text (For formatted strings take sprintf) + b_our_tracking.callback=b_our_tracking_cb; //Call b_our_tracking when the button get's pressed + gui_button_add(&b_our_tracking); //Register Button (and run the callback from now on) + b_ref_tracking.base.x1=b_our_tracking.base.x2+BUTTON_SPACING; + b_ref_tracking.base.y1=Y_FIRST-3; + b_ref_tracking.base.x2=AUTO; + b_ref_tracking.base.y2=AUTO; + b_ref_tracking.txtcolor=WHITE; + b_ref_tracking.bgcolor=HEX(0xFF2151); + b_ref_tracking.font=0; + b_ref_tracking.text="Reference Tracking"; + b_ref_tracking.callback=b_ref_tracking_cb; + gui_button_add(&b_ref_tracking); + + //Second line of buttons + #define Y_SECOND Y_FIRST+25 + tft_print_line(10,Y_SECOND,BLACK,TRANSPARENT,0,"Photo mode:"); + + b_photo_mode.base.x1=X_TAB; + b_photo_mode.base.y1=Y_SECOND-3; + b_photo_mode.base.x2=AUTO; + b_photo_mode.base.y2=AUTO; + b_photo_mode.txtcolor=WHITE; + b_photo_mode.bgcolor=HEX(0x21B1FF); + b_photo_mode.font=0; + b_photo_mode.text="Photo Mode"; + b_photo_mode.callback=b_photo_mode_cb; + gui_button_add(&b_photo_mode); + + + //Third line of buttons + #define Y_THIRD Y_SECOND+25 + tft_print_line(10,Y_THIRD,BLACK,TRANSPARENT,0,"Tests:"); + + b_guitest.base.x1=X_TAB; + b_guitest.base.y1=Y_THIRD-3; + b_guitest.base.x2=AUTO; + b_guitest.base.y2=AUTO; + b_guitest.txtcolor=BLACK; + b_guitest.bgcolor=HEX(0x00FA21); + b_guitest.font=0; + b_guitest.text="Gui Test"; + b_guitest.callback=b_guitest_cb; + gui_button_add(&b_guitest); + + + b_pixytest.base.x1=b_guitest.base.x2+BUTTON_SPACING; + b_pixytest.base.y1=Y_THIRD-3; + b_pixytest.base.x2=AUTO; + b_pixytest.base.y2=AUTO; + b_pixytest.txtcolor=BLACK; + b_pixytest.bgcolor=HEX(0x00FA96); + b_pixytest.font=0; + b_pixytest.text="Pixy Test"; + b_pixytest.callback=b_pixytest_cb; + gui_button_add(&b_pixytest); + + + b_filetest.base.x1=b_pixytest.base.x2+BUTTON_SPACING; + b_filetest.base.y1=Y_THIRD-3; + b_filetest.base.x2=AUTO; + b_filetest.base.y2=AUTO; + b_filetest.txtcolor=BLACK; + b_filetest.bgcolor=HEX(0x00FAC4); + b_filetest.font=0; + b_filetest.text="File Test"; + b_filetest.callback=b_filetest_cb; + gui_button_add(&b_filetest); + + + //Bottom line + tft_draw_line(0,145,319,145,BLACK); + tft_print_line(10,150,BLUE,TRANSPARENT,0,"Powered by"); + tft_draw_bitmap_file_unscaled(10,165,"pixy_small.bmp"); + tft_draw_bitmap_file_unscaled(165,165,"stm_small.bmp"); } static void leave(void* screen) { + gui_button_remove(&b_our_tracking); + gui_button_remove(&b_ref_tracking); + gui_button_remove(&b_photo_mode); gui_button_remove(&b_guitest); gui_button_remove(&b_pixytest); gui_button_remove(&b_filetest); diff --git a/common/app/screen_main.h b/common/app/screen_main.h index 94bbdef..8cc9bff 100644 --- a/common/app/screen_main.h +++ b/common/app/screen_main.h @@ -18,7 +18,7 @@ /*@{*/ /** - * @defgroup main Main + * @defgroup main Main (Screen) * The Main Screen is the start-screen for the application */ /*@{*/ @@ -31,4 +31,5 @@ */ SCREEN_STRUCT* get_screen_main(); -/*@}@}*/ +/*@}*/ +/*@}*/ diff --git a/common/app/screen_photomode.c b/common/app/screen_photomode.c new file mode 100644 index 0000000..eb41ed5 --- /dev/null +++ b/common/app/screen_photomode.c @@ -0,0 +1,166 @@ +#include "screen_photomode.h" +#include "screen_photomode_save.h" +#include "button.h" +#include "tft.h" +#include "touch.h" +#include "pixy.h" +#include "system.h" +#include "pixy_helper.h" + +static bool pixy_connected = false; //Whether or not the pixy cam is currently connected + +static BUTTON_STRUCT b_back; //Button to navigate back +static BUTTON_STRUCT b_save; //Button to save the current image +static TOUCH_AREA_STRUCT a_area; //Touch Area, where the frame is drawn. Used to drag the image around +static bool subMenu=false; //Whether or not we left the current screen for a submenu + +//Callback for when the user presses the "back" button +static void b_back_cb(void* button) { + subMenu = false; //we're not entering a submenu + gui_screen_back(); //navigate back to the previous screen +} + +//Callback for when the user presses the "save" button +static void b_save_cb(void* button) { + subMenu = true; //we're entering a submenu + gui_screen_navigate(get_screen_photomodesave()); //navigate to the save screen +} + +static POINT_STRUCT pixy_pos; //The current position of pixy's servos +static POINT_STRUCT old_pos; //The last touch position on the screen + +//Callback for when the user drags the image around +static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) { + POINT_STRUCT p = touch_get_last_point(); //get the last touched point + switch(triggeredAction) { + case PEN_ENTER: + case PEN_DOWN: + old_pos = p; //If the user "newly" enters the touch area, we set the "last" position to the current + break; + case PEN_MOVE: //the user is moving around, he entered the screen a while ago (old_pos is set) + { + int16_t deltaX = p.x - old_pos.x; //Calculate x difference between last and current touch + int16_t deltaY = p.y - old_pos.y; //Calculate y difference between last and current touch + old_pos=p; //store the current touch point for the next time + //printf("%d %d\n",deltaX,deltaY); + if(pixy_connected) { + //Calculate new servo coordinates. 2 is just a proportional factor + int16_t new_x = pixy_pos.x+deltaX*2; + int16_t new_y = pixy_pos.y-deltaY*2; + + //check limits + if(new_x<0) new_x=0; + if(new_x>1000) new_x=1000; + if(new_y<0) new_y=0; + if(new_y>1000) new_y=1000; + + //set pixy_pos so that the main routine can send it to the servos + pixy_pos.x = new_x; + pixy_pos.y= new_y; + } + } + break; + case PEN_UP: + case PEN_LEAVE: + //printf("Leave/up\n"); + break; + default: break; + } + +} + +//Callback for when the screen is entered/loaded +static void enter(void* screen) { + tft_clear(WHITE); + + tft_print_line(5,5,BLACK,TRANSPARENT,0,"Drag the image around and "); + + //Back button + b_back.base.x1=5; //Start X of Button + b_back.base.y1=19; //Start Y of Button + b_back.base.x2=AUTO; //Auto Calculate X2 with String Width + b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height + b_back.txtcolor=WHITE; //Set foreground color + b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) + b_back.font=0; //Select Font + b_back.text="Back"; //Set Text (For formatted strings take sprintf) + b_back.callback=b_back_cb; //Call b_back_cb as Callback + gui_button_add(&b_back); //Register Button (and run the callback from now on) + + //Save button + b_save.base.x1=190; + b_save.base.y1=3; + b_save.base.x2=AUTO; + b_save.base.y2=AUTO; + b_save.txtcolor=WHITE; + b_save.bgcolor=HEX(0x1010AE); + b_save.font=0; + b_save.text="Save it!"; + b_save.callback=b_save_cb; + gui_button_add(&b_save); + + //Frame Coordinates: topleft = (1,40); bottomright = (318,238) + //Leave a 10px border for the area + + //Area to drag the image around + a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE; + a_area.x1 = 11; + a_area.y1 = 50; + a_area.x2 = 308; + a_area.y2 = 228; + a_area.callback = touchCB; + touch_register_area(&a_area); + + //Pixy stuff + pixy_connected = (pixy_init()==0); //try to connect to pixy + if(pixy_connected && !subMenu) { //pixy is connected, but we are not coming from a submenu + pixy_pos.x=pixy_pos.y=500; //reset servo positions to center + } +} + +//Callback for when the screen is left/unloaded +static void leave(void* screen) { + //remove buttons and touch area. + gui_button_remove(&b_back); + gui_button_remove(&b_save); + touch_unregister_area(&a_area); +} + +//Callback for when the screen should be updated +//This is the main loop of the screen. This method will be called repeatedly +static void update(void* screen) { + //Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'( + + if(!pixy_connected) { //Pixy not connected + pixy_close(); //Ensure that all pixy resources are freed (failsafe) + if(pixy_init()==0) { //try to connect to pixy + pixy_connected=true; + if(!subMenu) { //we're not coming from a submenu + pixy_pos.x=pixy_pos.y=500; //reset servo positions to center + } + printf("pixy (re)initialized\n"); + } + } + + if(pixy_connected) { //If we are connected (now) + pixy_service(); //Handle pending pixy events (e.g. color info retrival) + + pixy_render_full_frame(1,40); //render the pixy video at point (1,40) + + //set the servo positions to the coordinates form the touch interrupt + pixy_rcs_set_position(0,pixy_pos.x); + pixy_rcs_set_position(1,pixy_pos.y); + } +} + +//Declare screen callbacks +static SCREEN_STRUCT screen = { + enter, + leave, + update +}; + + +SCREEN_STRUCT* get_screen_photomode() { + return &screen; +} diff --git a/common/app/screen_photomode.h b/common/app/screen_photomode.h new file mode 100644 index 0000000..19545ac --- /dev/null +++ b/common/app/screen_photomode.h @@ -0,0 +1,22 @@ +#include "screen.h" + +/** + * @addtogroup screens + */ +/*@{*/ + +/** + * @defgroup photomode Photo Mode (Screen) + * The Photo Mode Screen allows taking snapshots of the current pixy cam feed + */ +/*@{*/ + +/** + * Returns a pointer to the photomode screen + * \sa gui_screen_navigate + * @return + */ +SCREEN_STRUCT* get_screen_photomode(); + +/*@}*/ +/*@}*/ diff --git a/common/app/screen_photomode_save.c b/common/app/screen_photomode_save.c new file mode 100644 index 0000000..d05c4c8 --- /dev/null +++ b/common/app/screen_photomode_save.c @@ -0,0 +1,301 @@ +#include "screen_photomode_save.h" +#include "filesystem.h" +#include "button.h" +#include "tft.h" +#include "touch.h" +#include "pixy.h" +#include "pixy_helper.h" +#include +#include + + +static BUTTON_STRUCT b_back; //Button to navigate back +static TOUCH_AREA_STRUCT a_area; //Touch area to select the save-file + +//Callback for when the user presses the "back" button +static void b_back_cb(void* button) { + gui_screen_back(); +} + +static int num_files_ok; //number of files into which we can write the image (size, flags ok) +static enum {init, error, showlist, picking, saving, done} state; //Current state of the screen state machine +static int fontheight; //The space between one line of text to the next +static int liststart; //The y-Coordinate of the Start of the File-List +static const char* picked_file; //The filename picked by the user, to save the image to + +//Linked list structure to save all files which are suitable for saving. +typedef struct FILE_LIST_ENTRY_S{ + char* filename; //Name of the file + struct FILE_LIST_ENTRY_S* next; //Pointer to the next entry in the list or NULL +} FILE_LIST_ENTRY; + +static FILE_LIST_ENTRY* files_ok; //Pointer to the head of the list + +//Callback for when the user selects a file to save the image into +static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) { + + int y = touch_get_last_point().y-liststart; //Calculate the y-Coordinate of the touch point relative to the start of the file-list + int elem = y/fontheight; //Calculate the file index + if(elem<0 | elem>= num_files_ok) return; //Check if the file index is valid (0,1,..,num_files_ok-1) + + //Search for the corresponding entry in the linked list + FILE_LIST_ENTRY* current_entry = files_ok; //Start walking through the list, starting by the head of the list + for(int i=0; inext; //traverse to the next file + } + + picked_file = current_entry->filename; //save the picked filename. It will be used by the statemachine in the main loop + touch_unregister_area(&a_area); //unregister the touch area, we no longer need it. No more interrupts will be fired. + state=saving; //Change the state of the statemachine +} + +//Text-Lines to show if we have no matching files (num_files_ok==0) +static const char* nomatch_text [] = { + "Due to limitations of the filesystem", + "implementation you can only write to", + "existing files.", + "", + "The files need to have a .bmp", + "extension and must be at least", + "189410 bytes (185kb) large.", + "Unfortunately there were no such", + "files found in the root directory.", + "", + "Please create some files and come", + "back again.", + NULL +}; + + +//Bitmap header for a 318x198x24bit windows bitmap. data starts at 0x7A (= after this header) +//This header has been taken from a white bitmap saved with gimp. +//Wikipedia has a pretty good description on the header: http://de.wikipedia.org/wiki/Windows_Bitmap +static unsigned char bmpheader_data[0x7A] = { + 0x42, 0x4d, 0xe2, 0xe3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00, + 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3e, 0x01, 0x00, 0x00, 0xc6, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xe3, + 0x02, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + +//Callback for when the screen is entered/loaded +static void enter(void* screen) { + tft_clear(WHITE); + + + #define X_OFS 5 + + //Back button + b_back.base.x1=X_OFS; //Start X of Button + b_back.base.y1=210; //Start Y of Button + b_back.base.x2=AUTO; //Auto Calculate X2 with String Width + b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height + b_back.txtcolor=WHITE; //Set foreground color + b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) + b_back.font=0; //Select Font + b_back.text="Back"; //Set Text (For formatted strings take sprintf) + b_back.callback=b_back_cb; //Call b_back_cb as Callback + gui_button_add(&b_back); //Register Button (and run the callback from now on) + + state =init; //Start with the init state + fontheight = tft_font_height(0)+2; //Save the height of the used font, for fast access + files_ok = NULL; //initialize the linked list with 0 elements + num_files_ok = 0; //we have zero! elements +} + +//Callback for when the screen should be updated +//This is the main loop of the screen. This method will be called repeatedly +static void update(void* screen) { + switch(state) { + case init: //Init State: The user just entered the screen + { + DIRECTORY_STRUCT* dir = filesystem_dir_open("."); //open root directory + if(dir==NULL) { //error while opening root directory + tft_print_line(X_OFS,5,BLACK,TRANSPARENT,0,"Error accessing Filesystem"); + state=error; + break; + } + + bool nomatch= true; //whether or not we have zero files which are suitable for saving + + for(int i=0; inum_files; i++) { //walk through all files in the directory + FILE_STRUCT* file = &(dir->files[i]); //Pointer to the current file/subdirectory + + //Ignore directories, archives, hidden files, system files and files we cannot write to + if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue; + + //ignore files which are not large enough + if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24) + + nomatch=false; //at least one file matches + break; + } + + if(nomatch) { //not one file is suitable for writing + int y=5; //y-Coordinate where to start writing the error text + int i=0; + while(nomatch_text[i]!=NULL) { //for every line in the big error array + //Write the line's text and go to the next line + tft_print_line(X_OFS,y+i*fontheight,BLACK,TRANSPARENT,0,nomatch_text[i]); + i++; + } + state = error; + } else { //we have a least one suitable file + state = showlist; + } + + filesystem_dir_close(dir); //free directory struct + } + break; + + case showlist: //Show List State: Where we load and present the suitable file's to the user in a list + { + DIRECTORY_STRUCT* dir2 = filesystem_dir_open("."); //Open the directory again + if(dir2==NULL) return; //Error on opening? This should never happen, since it's handled in the previous state + + int y = 5; //y-Coordinate where to start drawing/writing text/list-elements + + tft_print_line(X_OFS,y,BLACK,TRANSPARENT,0,"Pick a file to save the image to"); + y+=fontheight+5; + + tft_print_line(X_OFS,y,BLUE,TRANSPARENT,0,"Name Modified Size"); + y+=fontheight; + + liststart = y; //store the y coordinate of the start of the list away (used in toucharea callback) + num_files_ok = 0; //we start with 0 matching files + + FILE_LIST_ENTRY* current_entry = NULL; //We start with an empty list + for(int i=0; inum_files && num_files_ok<10; i++) { //go through all the files of the directory, abort if we have 10 matches + FILE_STRUCT* file = &(dir2->files[i]); + + //Ignore directories, archives, hidden files, system files and files we cannot write to + if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue; + + //ignore files which are not large enough + if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24) + + //Print out filename, modified date,time and file size + tft_print_formatted(X_OFS,y,BLACK, + TRANSPARENT,0,"%-16s %02u.%02u.%02u %02u:%02u:%02u %u", + file->fname, + file->fdate.day, + file->fdate.month, + (file->fdate.year+1980)%100, + file->ftime.hour, + file->ftime.min, + file->ftime.sec*2, + file->fsize); + + if(current_entry==NULL) { //The list is empty + current_entry = malloc(sizeof(FILE_LIST_ENTRY)); //create new entry + files_ok = current_entry; //assign it to the list head + } else { //there's a least one entry in the list + current_entry->next = malloc(sizeof(FILE_LIST_ENTRY)); //append entry to previous entry + current_entry = current_entry->next; //newly created entry is the current now. + } + current_entry->next = NULL; //we're at the end of the list (for now) + current_entry->filename = malloc(strlen(file->fname)+1); //allocate space for the filename + zero-termination + strcpy(current_entry->filename,file->fname); //copy filename (so that we can close the directory after scanning) + + //since we have found a suitable file we need to increment the position in the list + num_files_ok++; + y+=fontheight; + } + + //Touch area for file-selection (in the list) + a_area.hookedActions = PEN_UP; //we're only interested in PEN_UP events + a_area.x1 = X_OFS; //Left border + a_area.y1 = liststart; //Start where the list started + a_area.x2 = 320-X_OFS; //Right border + a_area.y2 = liststart+fontheight*num_files_ok; //stop at the end of the list + a_area.callback = touchCB; //execute our callback when PEN_UP occurs + touch_register_area(&a_area); //register the touch area and receive events from now on + + filesystem_dir_close(dir2); //we no longer need the directory struct, since we have our own linked list now + + state=picking; + } + break; + + case picking: //Picking State: Where we wait on the users file choice + pixy_service(); //Handle pending pixy events + //do nothing and wait on user to pick a file + break; + + case saving: //Saving State: Where we save the image to the selected file + { + FILE_HANDLE* file = filesystem_file_open(picked_file); //try to open the selected file + if(file==NULL) { //opening the file failed + tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Could not open %s",picked_file); + state=error; + break; + } + + filesystem_file_seek(file,0); //seek to the start of the file (optional?) + if(filesystem_file_write(file,bmpheader_data,0x7A)!=F_OK) { //Writing the header failed + tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file); + filesystem_file_close(file); + state=error; + break; + } + + if(pixy_save_full_frame(file)!=0) { //Writing the imagedata failed + tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file); + filesystem_file_close(file); + state=error; + break; + } + + //if we reach this point, we have written all data out successfully + + filesystem_file_close(file); //close/finalize the file + tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Image saved to %s",picked_file); + state = done; + } + break; + + case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton + case done: //Done State: When saving the file was successful + pixy_service(); //Handle pending pixy events + //wait on user to click the back button + break; + + } +} + +//Callback for when the screen is left/unloaded +static void leave(void* screen) { + gui_button_remove(&b_back); //Remove/Free the back button + + if(state==picking){ //The user left the screen in the "picking"-phase + touch_unregister_area(&a_area); //remove the touch area (for the list) + } + + if(state==picking|| state==saving || state==done) { //the user left the screen after we created the linked list + //Iterate through the linked list and free all resources + FILE_LIST_ENTRY* current_entry = files_ok; //start with the list head + while(current_entry!=NULL) { //while we're not at the end + FILE_LIST_ENTRY* temp = current_entry->next; //save the next pointer because we free the current element on the next line + free((void*)(current_entry->filename)); //free filename + free(current_entry); //free element itself + current_entry= temp; //advance + } + } +} + +//Declare screen callbacks +static SCREEN_STRUCT screen = { + enter, + leave, + update +}; + +SCREEN_STRUCT* get_screen_photomodesave() { + return &screen; +} diff --git a/common/app/screen_photomode_save.h b/common/app/screen_photomode_save.h new file mode 100644 index 0000000..6f71fa0 --- /dev/null +++ b/common/app/screen_photomode_save.h @@ -0,0 +1,23 @@ +#include "screen.h" + +/** + * @addtogroup screens + */ +/*@{*/ + +/** + * @defgroup photomodesave Photo Mode Save (Screen) + * The Photo Mode Save Screen helps the user saving a file to the filesystem + */ +/*@{*/ + +/** + * Returns a pointer to the photomode save screen + * \sa gui_screen_navigate + * @return + */ +SCREEN_STRUCT* get_screen_photomodesave(); + +/*@}*/ +/*@}*/ + diff --git a/common/app/screen_pixytest.c b/common/app/screen_pixytest.c index 7d5b0de..05b5402 100644 --- a/common/app/screen_pixytest.c +++ b/common/app/screen_pixytest.c @@ -1,173 +1,329 @@ #include "screen_pixytest.h" #include "button.h" +#include "numupdown.h" #include "tft.h" #include "touch.h" #include "pixy.h" -#include #include "system.h" +#include "pixy_helper.h" -static volatile bool pixy_connected = false; - +static volatile enum {detecting, idle,update_servos, update_ledcolor, update_ledcurrent} state; //Current state of the screen state machine static BUTTON_STRUCT b_back; -static BUTTON_STRUCT b_runstop; -static TOUCH_AREA_STRUCT a_area; + +static BUTTON_STRUCT b_servos_center; +static BUTTON_STRUCT b_servos_topleft; +static BUTTON_STRUCT b_servos_topright; +static BUTTON_STRUCT b_servos_bottomleft; +static BUTTON_STRUCT b_servos_bottomright; +static uint16_t servo_x; +static uint16_t servo_y; + +static BUTTON_STRUCT b_led_off; +static BUTTON_STRUCT b_led_white; +static BUTTON_STRUCT b_led_red; +static BUTTON_STRUCT b_led_green; +static BUTTON_STRUCT b_led_blue; +static uint32_t led_color; + +static uint32_t led_maxcurrent; +static NUMUPDOWN_STRUCT n_led_powerlimit; + static void b_back_cb(void* button) { gui_screen_back(); } - -static volatile bool pixy_running = false; -static bool old_pixy_running= false; -static void b_runstop_cb(void* button) { - pixy_running=!pixy_running; -} - - - - -static POINT_STRUCT pixy_pos; -static POINT_STRUCT old_pos; -static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) { - POINT_STRUCT p = touch_get_last_point(); - switch(triggeredAction) { - case PEN_ENTER: - case PEN_DOWN: - old_pos = p; - break; - case PEN_MOVE: - { - int16_t deltaX = p.x - old_pos.x; - int16_t deltaY = p.y - old_pos.y; - old_pos=p; - printf("%d %d\n",deltaX,deltaY); - if(pixy_connected) { - int16_t new_x = pixy_pos.x+deltaX*2; - int16_t new_y = pixy_pos.y-deltaY*2; - if(new_x<0) new_x=0; - if(new_x>1000) new_x=1000; - if(new_y<0) new_y=0; - if(new_y>1000) new_y=1000; - pixy_pos.x = new_x; - pixy_pos.y= new_y; - } +static void b_servos_center_cb(void* button) { + if(state==idle) { + servo_x=500; + servo_y=500; + state=update_servos; } - break; - case PEN_UP: - case PEN_LEAVE: - printf("Leave/up\n"); - default: break; - } - - } +static void b_servos_topleft_cb(void* button) { + if(state==idle) { + servo_x=0; + servo_y=0; + state=update_servos; + } +} + +static void b_servos_topright_cb(void* button) { + if(state==idle) { + servo_x=1000; + servo_y=0; + state=update_servos; + } +} + +static void b_servos_bottomleft_cb(void* button) { + if(state==idle) { + servo_x=0; + servo_y=1000; + state=update_servos; + } +} + +static void b_servos_bottomright_cb(void* button) { + if(state==idle) { + servo_x=1000; + servo_y=1000; + state=update_servos; + } +} + +static void b_led_off_cb(void* button) { + if(state==idle) { + led_color=0x000000; + state=update_ledcolor; + } +} + +static void b_led_white_cb(void* button) { + if(state==idle) { + led_color=0xFFFFFF; + state=update_ledcolor; + } +} + +static void b_led_red_cb(void* button) { + if(state==idle) { + led_color=0xFF0000; + state=update_ledcolor; + } +} + +static void b_led_green_cb(void* button) { + if(state==idle) { + led_color=0x00FF00; + state=update_ledcolor; + } +} + +static void b_led_blue_cb(void* button) { + if(state==idle) { + led_color=0x0000FF; + state=update_ledcolor; + } +} + +static void n_led_powerlimit_cb(void* numupdown, int16_t value) { + if(state==idle) { + led_maxcurrent=value; + state=update_ledcurrent; + } +} static void enter(void* screen) { tft_clear(WHITE); //Back button b_back.base.x1=10; //Start X of Button - b_back.base.y1=210; //Start Y of Button - b_back.base.x2=AUTO; //b_back.base.x1+160; //Auto Calculate X2 with String Width - b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height - b_back.txtcolor=WHITE; //Set foreground color - b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) - b_back.font=0; //Select Font - b_back.text="Back"; //Set Text (For formatted strings take sprintf) - b_back.callback=b_back_cb; //Call b_back_cb as Callback - gui_button_add(&b_back); //Register Button (and run the callback from now on) + b_back.base.y1=210; //Start Y of Button + b_back.base.x2=AUTO; //Auto Calculate X2 with String Width + b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height + b_back.txtcolor=WHITE; //Set foreground color + b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible) + b_back.font=0; //Select Font + b_back.text="Back"; //Set Text (For formatted strings take sprintf) + b_back.callback=b_back_cb; //Call b_back_cb as Callback + gui_button_add(&b_back); //Register Button (and run the callback from now on) - //Back button - b_runstop.base.x1=60; //Start X of Button - b_runstop.base.y1=210; //Start Y of Button - b_runstop.base.x2=AUTO; //b_runstop.base.x1+160; //Auto Calculate X2 with String Width - b_runstop.base.y2=AUTO; //Auto Calculate Y2 with String Height - b_runstop.txtcolor=WHITE; //Set foreground color - b_runstop.bgcolor=HEX(0xAE1010); //Set runstopground color (Don't take 255 or 0 on at least one channel, to make shadows possible) - b_runstop.font=0; //Select Font - b_runstop.text="Run/Stop"; //Set Text (For formatted strings take sprintf) - b_runstop.callback=b_runstop_cb; //Call b_runstop_cb as Callrunstop - gui_button_add(&b_runstop); //Register Button (and run the callrunstop from now on) + //Servo stuff + #define SERVO_BUTTON_Y 10 + #define SERVO_BUTTON_SPACING 5 + tft_print_line(5,SERVO_BUTTON_Y,BLACK,TRANSPARENT,0,"Servos:"); + b_servos_center.base.x1=55; + b_servos_center.base.y1=SERVO_BUTTON_Y-3; + b_servos_center.base.x2=AUTO; + b_servos_center.base.y2=AUTO; + b_servos_center.txtcolor=WHITE; + b_servos_center.bgcolor=HEX(0xAE1010); + b_servos_center.font=0; + b_servos_center.text="Center"; + b_servos_center.callback=b_servos_center_cb; + gui_button_add(&b_servos_center); - //Area test - a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE; - a_area.x1 = 0; - a_area.y1 = 0; - a_area.x2 = 317; - a_area.y2 = 197; - a_area.callback = touchCB; - touch_register_area(&a_area); + b_servos_topleft.base.x1=b_servos_center.base.x2+SERVO_BUTTON_SPACING; + b_servos_topleft.base.y1=SERVO_BUTTON_Y-3; + b_servos_topleft.base.x2=AUTO; + b_servos_topleft.base.y2=AUTO; + b_servos_topleft.txtcolor=WHITE; + b_servos_topleft.bgcolor=HEX(0xAE1010); + b_servos_topleft.font=0; + b_servos_topleft.text="ToLe"; + b_servos_topleft.callback=b_servos_topleft_cb; + gui_button_add(&b_servos_topleft); + + b_servos_topright.base.x1=b_servos_topleft.base.x2+SERVO_BUTTON_SPACING; + b_servos_topright.base.y1=SERVO_BUTTON_Y-3; + b_servos_topright.base.x2=AUTO; + b_servos_topright.base.y2=AUTO; + b_servos_topright.txtcolor=WHITE; + b_servos_topright.bgcolor=HEX(0xAE1010); + b_servos_topright.font=0; + b_servos_topright.text="ToRi"; + b_servos_topright.callback=b_servos_topright_cb; + gui_button_add(&b_servos_topright); + + b_servos_bottomleft.base.x1=b_servos_topright.base.x2+SERVO_BUTTON_SPACING; + b_servos_bottomleft.base.y1=SERVO_BUTTON_Y-3; + b_servos_bottomleft.base.x2=AUTO; + b_servos_bottomleft.base.y2=AUTO; + b_servos_bottomleft.txtcolor=WHITE; + b_servos_bottomleft.bgcolor=HEX(0xAE1010); + b_servos_bottomleft.font=0; + b_servos_bottomleft.text="BoLe"; + b_servos_bottomleft.callback=b_servos_bottomleft_cb; + gui_button_add(&b_servos_bottomleft); + + b_servos_bottomright.base.x1=b_servos_bottomleft.base.x2+SERVO_BUTTON_SPACING; + b_servos_bottomright.base.y1=SERVO_BUTTON_Y-3; + b_servos_bottomright.base.x2=AUTO; + b_servos_bottomright.base.y2=AUTO; + b_servos_bottomright.txtcolor=WHITE; + b_servos_bottomright.bgcolor=HEX(0xAE1010); + b_servos_bottomright.font=0; + b_servos_bottomright.text="BoRi"; + b_servos_bottomright.callback=b_servos_bottomright_cb; + gui_button_add(&b_servos_bottomright); + + //Led Color stuff + #define LED_COLOR_BUTTON_Y 35 + #define LED_COLOR_BUTTON_SPACING 5 + tft_print_line(5,LED_COLOR_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Color:"); + + b_led_off.base.x1=85; + b_led_off.base.y1=LED_COLOR_BUTTON_Y-3; + b_led_off.base.x2=AUTO; + b_led_off.base.y2=AUTO; + b_led_off.txtcolor=WHITE; + b_led_off.bgcolor=BLACK; + b_led_off.font=0; + b_led_off.text="Off"; + b_led_off.callback=b_led_off_cb; + gui_button_add(&b_led_off); + + b_led_white.base.x1=b_led_off.base.x2+LED_COLOR_BUTTON_SPACING; + b_led_white.base.y1=LED_COLOR_BUTTON_Y-3; + b_led_white.base.x2=AUTO; + b_led_white.base.y2=AUTO; + b_led_white.txtcolor=BLACK; + b_led_white.bgcolor=HEX(0xEEEEEE); + b_led_white.font=0; + b_led_white.text="White"; + b_led_white.callback=b_led_white_cb; + gui_button_add(&b_led_white); + + b_led_red.base.x1=b_led_white.base.x2+LED_COLOR_BUTTON_SPACING; + b_led_red.base.y1=LED_COLOR_BUTTON_Y-3; + b_led_red.base.x2=AUTO; + b_led_red.base.y2=AUTO; + b_led_red.txtcolor=WHITE; + b_led_red.bgcolor=HEX(0xEE0000); + b_led_red.font=0; + b_led_red.text="Red"; + b_led_red.callback=b_led_red_cb; + gui_button_add(&b_led_red); + + b_led_green.base.x1=b_led_red.base.x2+LED_COLOR_BUTTON_SPACING; + b_led_green.base.y1=LED_COLOR_BUTTON_Y-3; + b_led_green.base.x2=AUTO; + b_led_green.base.y2=AUTO; + b_led_green.txtcolor=WHITE; + b_led_green.bgcolor=HEX(0x00EE00); + b_led_green.font=0; + b_led_green.text="Green"; + b_led_green.callback=b_led_green_cb; + gui_button_add(&b_led_green); + + b_led_blue.base.x1=b_led_green.base.x2+LED_COLOR_BUTTON_SPACING; + b_led_blue.base.y1=LED_COLOR_BUTTON_Y-3; + b_led_blue.base.x2=AUTO; + b_led_blue.base.y2=AUTO; + b_led_blue.txtcolor=WHITE; + b_led_blue.bgcolor=HEX(0x0000EE); + b_led_blue.font=0; + b_led_blue.text="Blue"; + b_led_blue.callback=b_led_blue_cb; + gui_button_add(&b_led_blue); + + //Led MaxPower stuff + #define LED_POWER_BUTTON_Y 70 + tft_print_line(5,LED_POWER_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Maximum Current:"); + + //Num up down test + n_led_powerlimit.x=160; + n_led_powerlimit.y=LED_POWER_BUTTON_Y-7; + n_led_powerlimit.fgcolor=WHITE; + n_led_powerlimit.value = 10; + n_led_powerlimit.max=40; + n_led_powerlimit.min =0; + n_led_powerlimit.callback=n_led_powerlimit_cb; + gui_numupdown_add(&n_led_powerlimit); - //Pixy stuff - pixy_connected = (pixy_init()==0); //try to connect to pixy - if(pixy_connected) { - pixy_pos.x=pixy_pos.y=500; - } + state=detecting; } static void leave(void* screen) { gui_button_remove(&b_back); - gui_button_remove(&b_runstop); - touch_unregister_area(&a_area); -} + gui_button_remove(&b_servos_center); + gui_button_remove(&b_servos_topleft); + gui_button_remove(&b_servos_topright); + gui_button_remove(&b_servos_bottomleft); + gui_button_remove(&b_servos_bottomright); + gui_button_remove(&b_led_off); + gui_button_remove(&b_led_white); + gui_button_remove(&b_led_red); + gui_button_remove(&b_led_green); + gui_button_remove(&b_led_blue); + gui_numupdown_remove(&n_led_powerlimit); -int pixy_led_test(); -int pixy_frame_test(); +} static void update(void* screen) { - - //Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'( - - if(!pixy_connected) { //Pixy not connected - pixy_close(); //Ensure that all pixy resources are freed (failsafe) - if(pixy_init()==0) { //try to connect to pixy - pixy_connected=true; - pixy_pos.x=pixy_pos.y=500; - printf("pixy reinitialized\n"); - } - } - - if(pixy_connected) { - pixy_service(); //Send/receive event data from/to pixy failed - - - if(pixy_frame_test()!=0) { - pixy_connected=false; - } - - /*if(pixy_led_test()!=0) { - pixy_connected=false; - }*/ - - if(!pixy_running) { - pixy_rcs_set_position(0,pixy_pos.x); - pixy_rcs_set_position(1,pixy_pos.y); - } - - if(pixy_running!=old_pixy_running) { - old_pixy_running=pixy_running; - if(pixy_running) { //start tracking - - int32_t response; - int return_value; - return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS); - - } else { //stop tracking + switch(state) { + case detecting: //Detecting State: Where we try to connect to the pixy + if(pixy_init()==0) { //Pixy connection ok int32_t response; int return_value; return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS); + pixy_led_set_max_current(10); + + state = idle; //Go to next state } + break; + case idle: + pixy_service(); + break; + case update_servos: + pixy_rcs_set_position(0,servo_x); + pixy_rcs_set_position(1,servo_y); + state = idle; + break; + + case update_ledcolor: + { + int32_t response; + int return_value; + return_value = pixy_command("led_set", INT32(led_color), END_OUT_ARGS, &response, END_IN_ARGS); + state = idle; } - - //system_delay(500); - } + break; + + case update_ledcurrent: + pixy_led_set_max_current(led_maxcurrent); + state = idle; + break; + } + } @@ -184,157 +340,3 @@ SCREEN_STRUCT* get_screen_pixytest() { -//----------------------------------------------------------------------------------------------------------------- - -int colorind; -const uint32_t colors [] = {0xFF0000, 0x00FF00,0x0000FF,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF,0x000000}; -const int num_colors = sizeof(colors)/sizeof(uint32_t); - -int pixy_led_test() { - if(colorind==0) { - pixy_led_set_max_current(5); - } - - int32_t response; - int return_value; - return_value = pixy_command("led_set", INT32(colors[colorind++]), END_OUT_ARGS, &response, END_IN_ARGS); - colorind%=num_colors; - - if(return_value!=0) { - colorind=0; //reset color ind, to start at zero when plugging pixy in again - } - - return return_value; -} - -//---------------------------------------------------------------------------------------------------------------------------- - -int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame); - - -int pixy_frame_test() { - - uint8_t* videodata; - int32_t response; - int32_t fourccc; - int8_t renderflags; - uint16_t xwidth; - uint16_t ywidth; - uint32_t size; - - - int return_value = pixy_command("cam_getFrame", // String id for remote procedure - INT8(0x21), // mode - INT16(0), // xoffset - INT16(0), // yoffset - INT16(320), // width - INT16(200), // height - END_OUT_ARGS, // separator - &response, // pointer to mem address for return value - &fourccc, - &renderflags, - &xwidth, - &ywidth, - &size, - &videodata, // pointer to mem address for returned frame - END_IN_ARGS); - - if(return_value==0) { - return_value = renderBA81(renderflags,xwidth,ywidth,size,videodata); - } - - return return_value; -} - - - - -void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b) -{ - if (y&1) - { - if (x&1) - { - *r = *pixel; - *g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2; - *b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2; - } - else - { - *r = (*(pixel-1)+*(pixel+1))>>1; - *g = *pixel; - *b = (*(pixel-width)+*(pixel+width))>>1; - } - } - else - { - if (x&1) - { - *r = (*(pixel-width)+*(pixel+width))>>1; - *g = *pixel; - *b = (*(pixel-1)+*(pixel+1))>>1; - } - else - { - *r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2; - *g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2; - *b = *pixel; - } - } - -} - - - - - - -int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame) -{ - uint16_t x, y; - uint8_t r, g, b; - - - // skip first line - frame += width; - - // don't render top and bottom rows, and left and rightmost columns because of color - // interpolation - //uint32_t decodedimage[(width-2)*(height-2)]; - uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2)); - - if(decodedimage==NULL) { //not enough free space to decode image in memory - //decode & render image pixel by pixel - uint16_t* line = decodedimage; - for (y=1; ystop(tracking_current); //stop tracking + pixy_led_set_RGB(0,0,0); + } +} + +//Callback for when the screen should be updated +//This is the main loop of the screen. This method will be called repeatedly +static void update(void* screen) { + switch(state) { + case detecting: //Detecting State: Where we try to connect to the pixy + if(pixy_init()==0) { //Pixy connection ok + state = init; //Go to next state + } + break; + + case init: //Init State: Where we start the tracking + tracking_current->start(tracking_current); + state=tracking; + break; + + case tracking: //Tracking state: Where we render the frame and the tracked objects + pixy_service(); //Receive events (e.g. block-data) from pixy + + if(pixy_blocks_are_new()) { //There are new blocks available + if(frame_visible) { //If the user want's us to draw the video data + pixy_render_full_frame(FRAME_START_X,FRAME_START_Y); + } else { //the user want's a colored background + tft_fill_rectangle(FRAME_START_X,FRAME_START_Y,FRAME_END_X,FRAME_END_Y,RGB(200,200,200)); + } + + #define BLOCK_BUFFER_SIZE 5 //The maximum amount of blocks that we want to receive + struct Block blocks[BLOCK_BUFFER_SIZE]; //Storage to receive blocks from pixy + int blocks_received= pixy_get_blocks(BLOCK_BUFFER_SIZE,blocks); //Try to receive up to BLOCK_BUFFER_SIZE Blocks from pixy + + if(blocks_received>=0) { //block receiving ok + tracking_current->update(tracking_current,blocks,blocks_received); //apply tracking + + //Draw blocks + for(int i=0; ix-1+FRAME_START_X -block->width/2; //Calculate x-Coordinate on the display + uint16_t y = block->y-1+FRAME_START_Y -block->height/2; //Calculate y-Coordinate on the display + tft_draw_rectangle(x,y,x+block->width-1, y+block->height-1,WHITE); //Draw a white rectangle + } + } + } + break; + + case preselecting: //Pre-Selecting State: Where we set up the color region selection + { + tracking_current->stop(tracking_current); //Stop tracking + + pixy_render_full_frame(FRAME_START_X,FRAME_START_Y); //Render one frame + + touch_register_area(&a_area); //Register touch area and receive events from now on + point1_valid=false; //we start with an invalid point1 + + b_select.text="Abort"; //Change the button text to "Abort" + gui_button_redraw(&b_select); //redraw button + + state = selecting; //The user can now select a region + } + break; + + case selected: //Selected State: Where we send the users selection to pixy + { + //Ensure that (x1,y1) represent the top-left point and (x2,y2) the bottom-right. + unsigned int tmp; + if(point1.x > point2.x){ + tmp = point1.x; + point1.x = point2.x; + point2.x = tmp; + } + + if(point1.y > point2.y){ + tmp = point1.y; + point1.y = point2.y; + point2.y = tmp; + } + //Send pixy the selected region + pixy_cc_set_region(1,point1.x,point1.y,point2.x-point1.x,point2.y-point1.y); + } + //no break here: We want the following code to be executed as well + + case abortselecting: //Abort-Selecting State: Where we deinitialize the stuff we used for region selection + { + touch_unregister_area(&a_area); //Remove the touch area. We'll no longer receive touch events + + b_select.text="Select Color"; //Change the button text back to "Select Color" + gui_button_redraw(&b_select); //redraw button + + tracking_current->start(tracking_current); //Start tracking again + state=tracking; + } + break; + + case selecting: //Selecting State: Where we wait on the user to select a color region + pixy_service(); //receive pixy events + //wait on user to select the image area + break; + + case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton + //wait on user to click the back button + break; + } +} + +//Declare screen callbacks +static SCREEN_STRUCT screen = { + enter, + leave, + update +}; + + +SCREEN_STRUCT* get_screen_tracking() { + return &screen; +} diff --git a/common/app/screen_tracking.h b/common/app/screen_tracking.h new file mode 100644 index 0000000..5a2dbde --- /dev/null +++ b/common/app/screen_tracking.h @@ -0,0 +1,37 @@ +#include "screen.h" + +/** + * @addtogroup screens + */ +/*@{*/ + +/** + * @defgroup tracking Tracking (Screen) + * The Tracking-Screen shows the object-tracking and allows some configuration + */ +/*@{*/ + + +/** + * Enum which contains the available tracking implementations + */ +enum Tracking_Implementation { + OUR_TRACKING, //!< Our own tracking PID implementation + REFERENCE_TRACKING//!< Pixy's internal tracking implementation +}; + +/** + * Sets the current Mode/Tracking Implementation. Call this before using the screen obtained by get_screen_tracking() + * @param impl The new mode + */ +void tracking_set_mode(enum Tracking_Implementation impl); + +/** + * Returns a pointer to the tracking screen + * \sa gui_screen_navigate + * @return + */ +SCREEN_STRUCT* get_screen_tracking(); + +/*@}*/ +/*@}*/ diff --git a/common/filesystem/filesystem.c b/common/filesystem/filesystem.c index 1cd803f..9d88b3b 100644 --- a/common/filesystem/filesystem.c +++ b/common/filesystem/filesystem.c @@ -10,7 +10,7 @@ DIRECTORY_STRUCT* filesystem_dir_open(const char* path) { } void filesystem_dir_close(DIRECTORY_STRUCT* dir) { - filesystem_dir_close(dir); + ll_filesystem_dir_close(dir); } FILE_HANDLE* filesystem_file_open(const char* filename) { diff --git a/common/filesystem/filesystem.h b/common/filesystem/filesystem.h index 164d76e..bb7b413 100644 --- a/common/filesystem/filesystem.h +++ b/common/filesystem/filesystem.h @@ -4,68 +4,144 @@ #include #include +/** + * @defgroup filesystem Filesystem + * The Filesystem Module provides access to files and directories of a the native filesystem. + */ +/*@{*/ + +/** + * File Attributes used by implementation + * See http://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#attributes for detailed description + */ typedef enum { - F_DIR=1, - F_RDO=2, - F_HID=4, - F_SYS=8, - F_ARC=16 + F_RDO=0x01,//!< File is readonly. You cannot write to it + F_HID=0x02,//!< File is hidden + F_SYS=0x04,//!< File is a system file + F_DIR=0x10,//!< It's a directory and not a file + F_ARC=0x20 //!< File has the archive flag set (probably unused) } FILE_ATTRIBUTES; - +/** + * Structure which represents last modified date of a file / directory + */ typedef struct { - unsigned year : 7; //year from 1980 (0..127) - unsigned month: 4; //month (1..12) - unsigned day: 5; //day (1..31) + unsigned year : 7; //!< year from 1980 (0..127) + unsigned month: 4; //!< month (1..12) + unsigned day: 5; //!< day (1..31) } FILE_DATE_STRUCT; - +/** + * Structure which represents last modified time of a file / directory + */ typedef struct { - unsigned hour : 5; //hour (0..23) - unsigned min: 6; //minute (0..59 - unsigned sec: 5; //second/2 (0..29) + unsigned hour : 5; //!< hour (0..23) + unsigned min: 6; //!< minute (0..59 + unsigned sec: 5; //!< second/2 (0..29) } FILE_TIME_STRUCT; - +/** + * Structure which represents a file/directory entry. \sa DIRECTORY_STRUCT + */ typedef struct { - uint32_t fsize; /* File size */ - FILE_DATE_STRUCT fdate; /* Last modified date */ - FILE_TIME_STRUCT ftime; /* Last modified time */ - uint8_t fattrib; /* Attribute */ - char* fname; /* File name */ + uint32_t fsize; //!< File size in bytes. 0 for directories + FILE_DATE_STRUCT fdate; //!< Last modified date + FILE_TIME_STRUCT ftime; //!< Last modified time + uint8_t fattrib; //!< File/Directory Attributes + char* fname; //!< File/Directory name } FILE_STRUCT; +/** + * Structure which represents an open directory with all it's entries. \sa filesystem_dir_open + */ typedef struct { - const char* path; - uint16_t num_files; - FILE_STRUCT* files; + const char* path; //!< Directory path (absolute) + uint16_t num_files; //!< Number of files/directories in this directory + FILE_STRUCT* files; //!< An array with \ref num_files FILE_STRUCT entries } DIRECTORY_STRUCT; +/** + * Structure which represents an open file. \sa filesystem_file_open + */ typedef struct { - const char* fname; - uint32_t fpos; - uint32_t fsize; + const char* fname; //!< The absolute file name + uint32_t fpos; //!< The current byte-position in the file. \sa filesystem_file_seek + uint32_t fsize; //!< The total file size in bytes } FILE_HANDLE; +/** + * Enum to represent the success or error-code of the filesystem_file_* functions + */ typedef enum { - F_OK, - F_EOF, - F_EACCESS, - F_INVALIDPARAM, - F_DISKERROR + F_OK, //!< Everything ok + F_EOF, //!< The write/read operation tried to write/read past the end of the file. This is not a fatal error. + F_EACCESS, //!< The file can not be read/written due to access problems. This is a fatal error. + F_INVALIDPARAM,//!< You passed invalid parameters to the function + F_DISKERROR //!< A lowlevel disk-error occoured. This is a fatal error. } FILE_STATUS; - +/** + * Initializes the filesystem. + * Call this method before using any filesystem_* functions + * @return true on success + */ bool filesystem_init(); +/** + * Opens a directory and returns a structure which contains all files/subdirectories. \sa filesystem_dir_close() + * @param path The absolute path to the directory to open/read + * @return A Pointer to an initialized DIRECTORY_STRUCT on success, NULL on error + */ DIRECTORY_STRUCT* filesystem_dir_open(const char* path); + +/** + * Closes a previously opened directory. Free's all allocated resources. + * @param dir A Pointer to a DIRECTORY_STRUCT obtained by filesystem_dir_open(). + */ void filesystem_dir_close(DIRECTORY_STRUCT* dir); + +/** + * Opens a file for read/writing. \note Depending on the implementation you may only open one file at a time + * @param filename The absolute file path + * @return A Pointer to a FILE_HANDLE on success, NULL on error. + */ FILE_HANDLE* filesystem_file_open(const char* filename); + +/** + * Closes a file. + * @param handle The FILE_HANDLE obtained by filesystem_file_open() + */ void filesystem_file_close(FILE_HANDLE* handle); + +/** + * Set's the read/write position to a new position + * @param handle The FILE_HANDLE obtained by filesystem_file_open() + * @param offset The new read/write position in bytes (absolute). + * @return \ref F_OK on success, an error Code otherwise. + */ FILE_STATUS filesystem_file_seek(FILE_HANDLE* handle, uint32_t offset); + +/** + * Reads some bytes from an open file. + * @param handle The FILE_HANDLE obtained by filesystem_file_open() + * @param buf The Buffer to write the bytes to + * @param size The number of bytes to read + * @return \ref F_OK on success, \ref F_EOF if less than \p size bytes could be read, an error Code otherwise. + */ FILE_STATUS filesystem_file_read(FILE_HANDLE* handle, uint8_t* buf, uint32_t size); + +/** + * Writes some bytes to a open file. + * \note Depending on the implementation the file may not be shrinked or expanded. + * @param handle The FILE_HANDLE obtained by filesystem_file_open() + * @param buf The Buffer to take the bytes from + * @param size The number of bytes to write + * @return \ref F_OK on success, \ref F_EOF if less than \p size bytes could be written, an error Code otherwise. + */ FILE_STATUS filesystem_file_write(FILE_HANDLE* handle, uint8_t* buf, uint32_t size); +/*@}*/ + #endif /* FILESYSTEM_H */ diff --git a/common/gui/button.c b/common/gui/button.c index e450b58..8abffcb 100644 --- a/common/gui/button.c +++ b/common/gui/button.c @@ -3,187 +3,152 @@ #include "button.h" #include -#define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add 1/3 off Full Value) +/* The Idea is as follows: + * When the user add's a button we create a touch area for that region and wait for PEN_DOWN events. + * Once the user puts the pen down in this area we'll redraw the button with different shadows (feedback) + * and we'll now wait on PEN_UP or PEN_LEAVE events. + * If the user takes the pen away while in the area (PEN_UP), we call the provided user callback + * Otherwise (PEN_LEAVE) we only restore the initial shadows + */ +/* Possible improvements: + * Move the button by 1 pixel while he is pressed, to create a "full 3d" experience + * Add events for the case when the button is pressed for a long time, without release + */ + +//Method to calculate the shadow colors used to create the "3d" effect +void calculate_shadows(uint16_t bgcolor, uint16_t* light_shadow, uint16_t* dark_shadow) { + #define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add/Subtract 1/3 off Full Value) + + uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows) + uint8_t r,g,b; + + //separate the channels of the 16-bit rgb565 color + r=(bgcolor&0xF800)>>11; + g=(bgcolor&0x07E0)>>5; + b=(bgcolor&0x001F)>>0; + + //For the light shadow color: + if((r + 0x1F/BRIGHTNESS_VAL) > 0x1F) //Adding one third would exceed the maximum of the red channel + c_light=0xF800; //Use full red + else //adding one third to the red channel is fine + c_light=(r+0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but add one third + if((g + 0x3F/BRIGHTNESS_VAL) > 0x3F) //same for the green channel + c_light|=0x07E0; + else + c_light|=(g+0x3F/BRIGHTNESS_VAL)<<5; + if((b + 0x1F/BRIGHTNESS_VAL) > 0x1F) //and the blue channel + c_light|=0x0018; + else + c_light|=(b+0x1F/BRIGHTNESS_VAL)<<0; + + //For the dark shadow color + if(r > (0x1F/BRIGHTNESS_VAL)) //Subtracting one third would NOT exceed the minimum of the red channel + c_dark=(r-0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but subtract one third + else //Subtracting one third would give us a number below zero + c_dark=0x0000; //use no red channel + if(g > (0x3F/BRIGHTNESS_VAL)) //Same for the green channel + c_dark|=(g-0x3F/BRIGHTNESS_VAL)<<5; + if(b > (0x1F/BRIGHTNESS_VAL)) //and the blue channel + c_dark|=(b-0x1F/BRIGHTNESS_VAL)<<0; + + //Assign the calculated shadows to out parameters + if(light_shadow!=NULL) *light_shadow = c_light; + if(dark_shadow!=NULL) *dark_shadow = c_dark; + +} + +//Callback which is called when the user touches the touch-area we created for the button void buttons_cb(void* touchArea, TOUCH_ACTION triggeredAction) -//Method shared between normal Buttons and Bitmap Buttons-> Look at comment in headerfile for explanation. { TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea; BUTTON_STRUCT* button = (BUTTON_STRUCT*)touchArea; - unsigned int c1,c2; - unsigned char r,g,b; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c1=0xF800; - else - c1=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c1|=0x07E0; - else - c1|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c1|=0x0018; - else - c1|=(b+0x1F/BRIGHTNESS_VAL)<<0; - if(r > (0x1F/BRIGHTNESS_VAL)) - c2=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c2=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c2|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c2|=(b-0x1F/BRIGHTNESS_VAL)<<0; + + uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows) + calculate_shadows(button->bgcolor,&c_light,&c_dark); + switch(triggeredAction) { - case PEN_DOWN: - area->hookedActions=PEN_UP|PEN_LEAVE; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c2); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c2);//West - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c1); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c1); //Ost + case PEN_DOWN: //If the user touches the area for the "first time" + area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events + + //Draw shadows + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_dark); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_dark);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_light); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_light); //East break; - case PEN_UP: - case PEN_LEAVE: - area->hookedActions=PEN_DOWN; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c1); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c1);//West - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c2); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c2); //Ost - if(triggeredAction==PEN_UP && button->callback!=NULL) - button->callback(button); + case PEN_UP: //If the user took the pen away, while in the area (=button pressed!) + case PEN_LEAVE: //or the user "slided out" of the area + area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events + + //Draw inverse shadows + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East + + if(triggeredAction==PEN_UP && button->callback!=NULL) //If the button got "pressed" instead of left, and the user provided a callback + button->callback(button); //execute the user callback break; default:break; } } -bool gui_button_add(BUTTON_STRUCT* button)//Registers a button (fill Struct first). Return false if no more Space in the Pointertable (-->Change NUM_AREAS). +bool gui_button_add(BUTTON_STRUCT* button) { - if(touch_have_empty(1)) + if(touch_have_empty(1)) //Check if the touch module can handle one additional area { + //Calculate width and height of the button text unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); unsigned char strheight=tft_font_height(button->font); - button->base.hookedActions=PEN_DOWN; - button->base.callback = buttons_cb; - if(button->base.x2==AUTO) + + button->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events + button->base.callback = buttons_cb; //Use our own callback for the touch area events + + if(button->base.x2==AUTO) { //The user wants us to calculate the button width automatically + //Use string width + half of a character width as button width button->base.x2= button->base.x1 -1 + strwidth+(tft_font_width(button->font)/2); - else if((button->base.x2-button->base.x1+1)<(strwidth+2)) - return false; + } else if((button->base.x2-button->base.x1+1)<(strwidth+2)) { //the provided width is too small to fit the entire text + return false; //report error + } - if(button->base.y2==AUTO) + if(button->base.y2==AUTO) { //The user wants us to calculate the button height automatically + //Use one and a half character heights as button height button->base.y2=button->base.y1 -1 +strheight+(strheight/2); - else if((button->base.y2-button->base.y1+1)<(strheight+2)) + } else if((button->base.y2-button->base.y1+1)<(strheight+2)) { //the provided height is too small to fit the text return false; - gui_button_redraw(button); - return touch_register_area(&button->base); + } + gui_button_redraw(button); //call the redraw method, which will take care of drawing the entire button + return touch_register_area(&button->base); //Register the touch area and receive events for this button, from now on } - return false; + + return false; //no more touch areas left } void gui_button_redraw(BUTTON_STRUCT* button) { - unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); - unsigned char strheight=tft_font_height(button->font); - unsigned char r,g,b; - unsigned int c; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; + //Calculate text dimensions and shadow colors + unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); + unsigned char strheight=tft_font_height(button->font); + uint16_t c_light,c_dark; + calculate_shadows(button->bgcolor,&c_light,&c_dark); + + //Draw the background and the 4 lines (shadow colors) tft_fill_rectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor); - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c=0xF800; - else - c=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c|=0x07E0; - else - c|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c|=0x0018; - else - c|=(b+0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West - if(r > (0x1F/BRIGHTNESS_VAL)) - c=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c|=(b-0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East + + //Draw the text tft_print_line(button->base.x1+(button->base.x2-button->base.x1+1-strwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-strheight)/2,button->txtcolor,button->bgcolor,button->font,button->text); } + void gui_button_remove(BUTTON_STRUCT* button) { + //We only need to unregister the touch area, as we have not allocated anything else touch_unregister_area((TOUCH_AREA_STRUCT*)button); } - -/* -bool guiAddBitmapButton (BITMAPBUTTON_STRUCT* button) -{ - if(touchHaveEmpty(1)) - { - button->base.hookedActions=PEN_DOWN; - button->base.callback = buttons_cb; - if(button->base.x2==AUTO) - button->base.x2= button->base.x1 -1 + button->imgwidth + button->imgwidth/4; - else if((button->base.x2-button->base.x1+1)<(button->imgwidth+2)) - return false; - - if(button->base.y2==AUTO) - button->base.y2=button->base.y1 -1 +button->imgheight + button->imgheight/4; - else if((button->base.y2-button->base.y1+1)<(button->imgheight+2)) - return false; - guiRedrawBitmapButton(button); - return touchRegisterArea(&button->base); - } - return false; -} - -void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button) -{ - - unsigned char r,g,b; - unsigned int c; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; - tftFillRectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor); - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c=0xF800; - else - c=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c|=0x07E0; - else - c|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c|=0x0018; - else - c|=(b+0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West - if(r > (0x1F/BRIGHTNESS_VAL)) - c=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c|=(b-0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost - tftDrawBitmapUnscaledStreamedRaw(button->base.x1+(button->base.x2-button->base.x1+1-button->imgwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-button->imgheight)/2,button->imgwidth,button->imgheight,button->filename); -} -void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button) -{ - touchUnregisterArea((TOUCH_AREA_STRUCT*)button); -} - -*/ diff --git a/common/gui/button.h b/common/gui/button.h index b0d0bac..14aff59 100644 --- a/common/gui/button.h +++ b/common/gui/button.h @@ -1,6 +1,8 @@ #ifndef BUTTON_H #define BUTTON_H +#include "touch.h" + /** * @defgroup gui Gui * The Gui Module @@ -9,16 +11,21 @@ /** * @defgroup button Button - * The Button Gui-Element + * The Button Gui-Element is a clickable, rectangular box with a label inside. + * When it is pressed and released you will be notified via the provided callback. + */ + +/*@}*/ + +/** + * @addtogroup button */ /*@{*/ - -#include "touch.h" - /** * Prototype for Event Listeners (called when the button is pressed) + * \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance. * @param button The pointer to the BUTTON_STRUCT where to corresponding Button was pressed */ typedef void (*BUTTON_CALLBACK)(void *button); @@ -30,7 +37,7 @@ typedef void (*BUTTON_CALLBACK)(void *button); typedef struct { TOUCH_AREA_STRUCT base; //!< Basic geometry of the button. You only need to set the x1, y1, x2, y2 members of this struct. uint16_t bgcolor; //!< The 16-bit background color of the button - BUTTON_CALLBACK callback; //!< Callback + BUTTON_CALLBACK callback; //!< Callback which is executed when the button is pressed uint16_t txtcolor; //!< The 16-bit text color uint8_t font; //!< The number of the font to use const char *text; //!< The label of the button @@ -58,27 +65,7 @@ void gui_button_remove(BUTTON_STRUCT* button); */ void gui_button_redraw(BUTTON_STRUCT* button); -/* -bool guiAddBitmapButton(BITMAPBUTTON_STRUCT* button); -void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button); -void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button); -*/ - -/* -typedef struct { - TOUCH_AREA_STRUCT base; - unsigned int bgcolor; - BUTTON_CALLBACK callback; //Callback - unsigned char imgwidth; - unsigned char imgheight; - char* filename; -} BITMAPBUTTON_STRUCT; -*/ -//Notice that the first 3 Members are Equal, so it's possible to cast it to a BUTTON_STRUCT even if it's a BITMAPBUTTON_STRUCT (when changeing only the first 3 Members). - - - -/*@}@}*/ +/*@}*/ #endif /* BUTTON_H */ diff --git a/common/gui/checkbox.c b/common/gui/checkbox.c index 96325a6..3f0cb8a 100644 --- a/common/gui/checkbox.c +++ b/common/gui/checkbox.c @@ -3,29 +3,44 @@ #include "checkbox.h" #include -#define BRIGHTNESS_VAL 2 //How much the Brightness is in/decreased for checkbox shadows (3 -> Add 1/3 off Full Value) -#define ACTIVE_COLOR RGB(251,208,123) -#define BORDER_COLOR RGB(29,82,129) -#define BACKGROUND_COLOR WHITE +/* The idea is as follows: + * When the user creates a checkbox we create a touch area for that region and wait for PEN_DOWN events. + * Once the user puts the pen down in this area we'll redraw the checkbox with different shadows (feedback) + * and we'll now wait on PEN_UP or PEN_LEAVE events. + * If the user takes the pen away while in the area (PEN_UP), we toggle the checkbox and we call the provided user callback + * Otherwise (PEN_LEAVE) we only restore the initial shadows + */ + +#define ACTIVE_COLOR RGB(251,208,123) //shadow color (inside of border) +#define BORDER_COLOR RGB(29,82,129) //1px border color +#define BACKGROUND_COLOR WHITE //Background color + +//Callback which is called when the user touches the touch-area we created for the checkbox void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction) { TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea; CHECKBOX_STRUCT* checkbox = (CHECKBOX_STRUCT*)touchArea; switch(triggeredAction) { - case PEN_DOWN: - area->hookedActions=PEN_UP|PEN_LEAVE; + case PEN_DOWN: //If the user touches the area for the "first time" + area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events + + //Draw active shadows tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,ACTIVE_COLOR); tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,ACTIVE_COLOR); break; - case PEN_UP: - checkbox->checked=!checkbox->checked; - gui_checkbox_update(checkbox); - if(checkbox->callback!=NULL) - checkbox->callback(checkbox,checkbox->checked); - case PEN_LEAVE: - area->hookedActions=PEN_DOWN; + case PEN_UP: //If the user took the pen away, while in the area (=toggle checkbox!) + checkbox->checked=!checkbox->checked; //Toggle checkbox state + gui_checkbox_update(checkbox); //redraw/overdraw tickmark + if(checkbox->callback!=NULL) { //The user provided a callback + checkbox->callback(checkbox,checkbox->checked); //Call the provided callback with the new checked state + } + // no break statement here! + case PEN_LEAVE: //if the user "slided out" of the area + area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events + + //Draw inactive shadows tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR); tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,BACKGROUND_COLOR); break; @@ -35,48 +50,66 @@ void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction) bool gui_checkbox_add(CHECKBOX_STRUCT* checkbox) { - if(touch_have_empty(1)) + if(touch_have_empty(1)) //Check if the touch module can handle one additional area { unsigned char size=0; - checkbox->base.hookedActions=PEN_DOWN; - checkbox->base.callback = checkboxes_cb; + checkbox->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events + checkbox->base.callback = checkboxes_cb; //Use our own callback for the touch area events + + //Check the size of the checkbox if(checkbox->base.x2>checkbox->base.x1) - size = checkbox->base.x2 - checkbox->base.x1; + size = checkbox->base.x2 - checkbox->base.x1; //use width a as size if(checkbox->base.y2>checkbox->base.y1) { - if((checkbox->base.y2 - checkbox->base.y1)>size) - size = checkbox->base.y2 - checkbox->base.y1; + if((checkbox->base.y2 - checkbox->base.y1)>size) //height is larger than size + size = checkbox->base.y2 - checkbox->base.y1; //use height as size } - if((size&0x01)) - size++; + if(size==0) { //no size found (maybe swap x2 and x1 or y2 and y1 ?) + return false; //signal error + } + if((size&0x01)) //the size is an odd number + size++; //make size an even number + + //Correct x2,y2 so that the checkbox is quadratic checkbox->base.x2 = checkbox->base.x1 + size; checkbox->base.y2 = checkbox->base.y1 + size; - gui_checkbox_redraw(checkbox); - return touch_register_area(&checkbox->base); + + gui_checkbox_redraw(checkbox);//Call redraw method, which will take care of the drawing of the entire checkbox + + return touch_register_area(&checkbox->base); //Register the touch area and receive events for this checkbox, from now on } - return false; + + return false; //no more touch areas left } void gui_checkbox_redraw(CHECKBOX_STRUCT* checkbox) { + //Draw background and border tft_fill_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR); tft_draw_rectangle(checkbox->base.x1,checkbox->base.y1,checkbox->base.x2,checkbox->base.y2,BORDER_COLOR); - if(checkbox->checked) - gui_checkbox_update(checkbox); + + if(checkbox->checked) { //checkbox is currently checked + gui_checkbox_update(checkbox); //Call update method which will draw the tickmark + } } void gui_checkbox_remove(CHECKBOX_STRUCT* checkbox) { + //We only need to unregister the touch area, as we have not allocated anything else touch_unregister_area((TOUCH_AREA_STRUCT*)checkbox); } void gui_checkbox_update(CHECKBOX_STRUCT* checkbox) { - unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR; - unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14; + unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR; //color to use for the tickmark + + //helper points inside the checkbox + unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14; unsigned int yleft = checkbox->base.y2 - (xcent- checkbox->base.x1) - 1 ; unsigned int yright = checkbox->base.y2 - (checkbox->base.x2 - xcent) - 1 ; unsigned int ybot = checkbox->base.y2 - 4; + + //Draw tickmark as a 3pixel wide line tft_draw_line(checkbox->base.x1+3,yleft-1,xcent,ybot -1,c); tft_draw_line(checkbox->base.x1+3,yleft,xcent,ybot ,c); tft_draw_line(checkbox->base.x1+3,yleft+1,xcent,ybot + 1,c); diff --git a/common/gui/checkbox.h b/common/gui/checkbox.h index 7b1b8e2..9165f6c 100644 --- a/common/gui/checkbox.h +++ b/common/gui/checkbox.h @@ -1,6 +1,8 @@ #ifndef CHECKBOX_H #define CHECKBOX_H +#include "touch.h" + /** * @addtogroup gui */ @@ -8,26 +10,61 @@ /** * @defgroup checkbox Checkbox - * The Checkbox Gui-Element + * The Checkbox Gui-Element is a clickable, rectangular box with an optional tickmark inside of it. + * When the checkbox is pressed and released it's tick state changes and you will be notified via the provided callback. + */ +/*@}*/ + +/** + * @addtogroup checkbox */ /*@{*/ +/** + * Prototype for Event Listeners (called when the checkbox state has changed) + * \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance. + * @param checkbox The pointer to the CHECKBOX_STRUCT where to corresponding Checkbox has changed the state + * @param checked A boolean which indicates whether the checkbox is now checked or not. + */ +typedef void (*CHECKBOX_CALLBACK)(void *checkbox, bool checked); -typedef void (*CHECKBOX_CALLBACK)(void *checkbox, bool checked); //!< Function pointer used... +/** + * Structure to configure the Checkbox + */ typedef struct { - TOUCH_AREA_STRUCT base; - uint16_t fgcolor; - bool checked; - CHECKBOX_CALLBACK callback; //Callback + TOUCH_AREA_STRUCT base; //!< Basic geometry of the Checkbox. You only need to set the x1, y1, x2, y2 members of this struct. + uint16_t fgcolor; //!< The 16-bit color of the tickmark + bool checked; //!< A boolean which indicates whether or not the checkbox is currently checked. + CHECKBOX_CALLBACK callback; //!< Callback which is executed when the checkbox changes state } CHECKBOX_STRUCT; - +/** + * Adds a checkbox. Your Callback will be called from now on, if the checkbox changes state + * @param checkbox A Pointer to the preinitialized CHECKBOX_STRUCT + * @return true on success + */ bool gui_checkbox_add(CHECKBOX_STRUCT* checkbox); + +/** + * Removes the checkbox. You will no longer receive events for this checkbox. This function will not overdraw the region where the checkbox was located. + * @param checkbox A Pointer to the CHECKBOX_STRUCT + */ void gui_checkbox_remove(CHECKBOX_STRUCT* checkbox); + +/** + * Updates the checkbox. Call this function when you change the state of the checkbox through code. + * @param checkbox A Pointer to the CHECKBOX_STRUCT + */ void gui_checkbox_update(CHECKBOX_STRUCT* checkbox); + +/** + * Redraws the checkbox. Call this method if you have to redraw the entire screen or if you want to draw a checkbox on top of an image. + * @param checkbox A Pointer to the CHECKBOX_STRUCT + */ void gui_checkbox_redraw(CHECKBOX_STRUCT* checkbox); + #define CHECKBOX_WIN_FG_COLOR RGB(32,161,34) -/*@}@}*/ +/*@}*/ #endif /* CHECKBOX_H */ diff --git a/common/gui/numupdown.c b/common/gui/numupdown.c index a6c8024..e17a48b 100644 --- a/common/gui/numupdown.c +++ b/common/gui/numupdown.c @@ -6,39 +6,51 @@ #include //for offsetof macro #include //for abs +/* The idea is as follows: + * When the user add's a numupdown we create two buttons, one with a plus and one with a minus sign in it + * When the user presses one of the buttons we check and increase the value and execute the provided user callback + */ -#define BASE_COLOR RGB(90,90,90) + +#define BASE_COLOR RGB(90,90,90) //Background color for the whole element + +//Callback which is called when the user presses the "plus" button void button_up_cb(void* button) { + //Get the pointer to the numupdown: subtract the offset of the buttonUp member in the struct from the button pointer NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonUp); - if(element->valuemax) { - element->value++; - gui_numupdown_update(element); - if(element->callback!=NULL) { - element->callback(element,element->value); + + if(element->valuemax) { //old value lies below the maximum + element->value++; //let's increase the value + gui_numupdown_update(element); //and redraw everything + if(element->callback!=NULL) { //the user provided a callback + element->callback(element,element->value); //Call the user callback with the new value } } } +//Callback which is called when the user presses the "minus" button void button_down_cb(void* button) { + //Get the pointer to the numupdown: subtract the offset of the buttonDown member in the struct from the button pointer NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonDown); - if(element->value>element->min) { - element->value--; - gui_numupdown_update(element); - if(element->callback!=NULL) { - element->callback(element,element->value); + + if(element->value>element->min) { //old value lies above the minimum + element->value--; //let's decrease the value + gui_numupdown_update(element); //and redraw everything + if(element->callback!=NULL) { //the user provided a callback + element->callback(element,element->value); //Call the user callback with the new value } } } - +//Method to calculate the number of characters needed to print the provided number in decimal notation (with optional sign) static uint8_t calc_text_width(int16_t val) { uint8_t width = 1 + (val<0); //1 if positive, 2 if negative (to let space for sign) - val=abs(val); - while(val>=10) { - val/=10; - width++; + val=abs(val); //Make the number positive + while(val>=10) { //while we have two or more digits + val/=10; //remove one digit + width++; //add one character } return width; } @@ -46,24 +58,24 @@ static uint8_t calc_text_width(int16_t val) { bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) { - if(touch_have_empty(2)) //We require 2 TouchAreas (for Buttons) + if(touch_have_empty(2)) //Check if the touch module can handle two additional areas { - if(numupdown->min > numupdown->max) return false; - - if(numupdown->value > numupdown->max) { - numupdown->value = numupdown->max; + if(numupdown->min > numupdown->max) { //min is bigger than max? + return false; //invalid parameter } - if(numupdown->value < numupdown->min) { - numupdown->value = numupdown->min; - } else if(numupdown->value > numupdown->max) { - numupdown->value = numupdown->max; + + if(numupdown->value < numupdown->min) { //value is smaller than min? + numupdown->value = numupdown->min; //normalize value + } else if(numupdown->value > numupdown->max) { //value is bigger than max? + numupdown->value = numupdown->max; //normalize value } - uint8_t tw1 = calc_text_width(numupdown->max); - uint8_t tw2 = calc_text_width(numupdown->min); - if(tw2 > tw1) tw1 = tw2; - uint8_t width= tft_font_width(0)*(tw1+1); + uint8_t tw1 = calc_text_width(numupdown->max); //Calculate character width to render maximum value + uint8_t tw2 = calc_text_width(numupdown->min); //Calculate character width to render minimum value + if(tw2 > tw1) tw1 = tw2; //ensure tw1 contains the larger number of the two + uint8_t width= tft_font_width(0)*(tw1+1); //Calculate width of the number area + //Add "minus" button to the left side of the number area numupdown->buttonDown.base.x1=numupdown->x; numupdown->buttonDown.base.y1=numupdown->y; numupdown->buttonDown.base.x2=AUTO; @@ -74,6 +86,8 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) numupdown->buttonDown.txtcolor=WHITE; numupdown->buttonDown.callback = button_down_cb; gui_button_add(&numupdown->buttonDown); + + //Add "plus" button to the right side of the number area numupdown->buttonUp.base.x1=numupdown->buttonDown.base.x2+width+2; numupdown->buttonUp.base.y1=numupdown->y; numupdown->buttonUp.base.x2=AUTO; @@ -85,36 +99,45 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) numupdown->buttonUp.callback = button_up_cb; gui_button_add(&numupdown->buttonUp); + //Draw background and label of the number area tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR); tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value); return true; } - return false; + + return false; //not enough touch areas left } void gui_numupdown_remove(NUMUPDOWN_STRUCT* numupdown) - { +{ + //remove the two buttons, we have no other allocated resources gui_button_remove(&numupdown->buttonUp); gui_button_remove(&numupdown->buttonDown); - } +} + + +void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown) +{ + //redraw the two buttons + gui_button_redraw(&numupdown->buttonUp); + gui_button_redraw(&numupdown->buttonDown); + + //call update method which will take care of the number-area rendering + gui_numupdown_update(numupdown); +} void gui_numupdown_update(NUMUPDOWN_STRUCT* numupdown) - { - - +{ + //Calculate the number area width again (see above) uint8_t tw1 = calc_text_width(numupdown->max); uint8_t tw2 = calc_text_width(numupdown->min); if(tw2 > tw1) tw1 = tw2; uint8_t width= tft_font_width(0)*(tw1+1); + //Draw background and label of the number area tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR); tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value); - } +} + - void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown) - { - gui_button_redraw(&numupdown->buttonUp); - gui_button_redraw(&numupdown->buttonDown); - gui_numupdown_update(numupdown); - } diff --git a/common/gui/numupdown.h b/common/gui/numupdown.h index fd97667..5734787 100644 --- a/common/gui/numupdown.h +++ b/common/gui/numupdown.h @@ -1,6 +1,8 @@ #ifndef NUMUPDOWN_H #define NUMUPDOWN_H +#include "button.h" + /** * @addtogroup gui */ @@ -10,32 +12,62 @@ * @defgroup numupdown NummericUpDown * The NummericUpDown Gui Element */ +/*@}*/ + +/** + * @addtogroup numupdown + */ /*@{*/ +/** + * Prototype for Event Listeners (called when the NummericUpDown value has changed) + * \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance. + * @param numupdown The pointer to the NUMUPDOWN_STRUCT where to corresponding NummericUpDown has changed it's value + * @param value The new value of the NummericUpDown + */ +typedef void (*NUMUPDOWN_CALLBACK)(void *numupdown, int16_t value); -#include "button.h" - -typedef void (*NUMUPDOWN_CALLBACK)(void *numupdown, int16_t value); //!< Function pointer used... +/** + * Structure to configure the NummericUpDown + */ typedef struct { - uint16_t x; - uint16_t y; - uint16_t fgcolor; - int16_t value; - int16_t min; - int16_t max; - NUMUPDOWN_CALLBACK callback; //Callback + uint16_t x; //!< The x-Coordinate of the Top-Left Starting Point. + uint16_t y; //!< The y-Coordinate of the Top-Left Starting Point. + uint16_t fgcolor; //!< The 16-bit color of the value-text + int16_t value; //!< The current/default value + int16_t min; //!< The minimum possible value (inclusive) + int16_t max; //!< The maximum possible value (inclusive) + NUMUPDOWN_CALLBACK callback; //!< Callback which is executed when the value changes - //Internally used: - BUTTON_STRUCT buttonUp; - BUTTON_STRUCT buttonDown; + BUTTON_STRUCT buttonUp; //!< For internal use, don't change, don't initialize + BUTTON_STRUCT buttonDown; //!< For internal use, don't change, don't initialize } NUMUPDOWN_STRUCT; - +/** + * Adds a NummericUpDown. Your Callback will be called from now on, if the numupdown's value changes + * @param numupdown A Pointer to the preinitialized NUMUPDOWN_STRUCT + * @return true on success + */ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown); + +/** + * Removes the NummericUpDown. You will no longer receive events for this numupdown. This function will not overdraw the region where the numupdown was located. + * @param numupdown A Pointer to the NUMUPDOWN_STRUCT + */ void gui_numupdown_remove(NUMUPDOWN_STRUCT* numupdown); + +/** + * Updates the NummericUpDown. Call this function when you change the value/min/max of the numupdown through code. + * @param numupdown A Pointer to the NUMUPDOWN_STRUCT + */ void gui_numupdown_update(NUMUPDOWN_STRUCT* numupdown); + +/** + * Redraws the NummericUpDown. Call this method if you have to redraw the entire screen or if you want to draw a numupdown on top of an image. + * @param numupdown A Pointer to the NUMUPDOWN_STRUCT + */ void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown); -/*@}@}*/ +/*@}*/ #endif /* NUMUPDOWN_H */ diff --git a/common/gui/screen.c b/common/gui/screen.c index 23315f0..cc90cea 100644 --- a/common/gui/screen.c +++ b/common/gui/screen.c @@ -1,32 +1,40 @@ #include "screen.h" +/* The idea is as follows: + * We only call screen callbacks from the gui_screen_update() method, which is called from the applications main loop. + * Instructions to switch the screen will be delayed until the gui_screen_update() method is called again. + * This makes it safe to change the screen from an touch interrupt (e.g. button callback) + */ -static SCREEN_STRUCT* screen_list = NULL; -static SCREEN_STRUCT* screen_current = NULL; -static volatile SCREEN_STRUCT* screen_goto = NULL; +static SCREEN_STRUCT* screen_list = NULL; //Head of the linked list which stores the screen history. +static SCREEN_STRUCT* screen_current = NULL; //Pointer to the current screen (= tail of the list) +static volatile SCREEN_STRUCT* screen_goto = NULL; //Screen we should navigate to once we enter the gui_screen_update() method again SCREEN_STRUCT* gui_screen_get_current() { return screen_current; } + void gui_screen_update() { if(screen_goto!=NULL) { //we received the task to switch the screen SCREEN_STRUCT* go = (SCREEN_STRUCT*) screen_goto; //Backup volatile variable - screen_goto=NULL; - if(go->next!=NULL) { //we're going back - if(go->next!=screen_current) return; //list corrupted? - screen_current->on_leave(screen_current); - go->next=NULL; - } else { //we're going forward + screen_goto=NULL; //reset the "goto instruction", since we're processing it now + if(go->next!=NULL) { //The screen is not the last in the list, so we're going back + if(go->next!=screen_current) { //this condition should always be false + return; //list corrupted? + } + screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources + go->next=NULL; //remove the current screen from the list + } else { //we're going forward (to a new screen) if(screen_current!=NULL) { //this is not the first screen - screen_current->on_leave(screen_current); - screen_current->next = go; - } else { //first screen ever seen - screen_list=go; - } + screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources + screen_current->next = go; //append the new screen to the end of the list + } else { //first screen ever seen + screen_list=go; //set the new screen as list-head + } } - go->on_enter(go); - screen_current =go; + go->on_enter(go); //let the new screen allocate/register it's resources + screen_current = go; //the new screen is now the current screen. Transition done } if(screen_current!=NULL) { //A screen has been set @@ -37,16 +45,18 @@ void gui_screen_update() { bool gui_screen_navigate(SCREEN_STRUCT* screen) { - if(screen==NULL) return false; - screen->next = NULL; - screen_goto=screen; //send message to main loop, to switch the screen + if(screen==NULL) { //invalid argument passed + return false; + } + screen->next = NULL; //this will become the new tail of the list, so the next pointer must be NULL + screen_goto=screen; //"send message" to main loop, to switch the screen return true; } - - bool gui_screen_back() { - if(screen_list==NULL) return false; + if(screen_list==NULL) { //the list head is emtpy, nothing to go back to + return false; + } SCREEN_STRUCT* current = screen_list; SCREEN_STRUCT* last = NULL; //Find second last element in list @@ -54,15 +64,8 @@ bool gui_screen_back() { last = current; current = current->next; } - if(last==NULL) return false; //There's only a single screen. - if(current!=screen_current) return false; //List corrupted? - screen_goto=last; //send message to main loop, to switch the screen + if(last==NULL) return false; //There's only a single screen, there's no going back here + if(current!=screen_current) return false; //The last entry in the list is not the current screen. List corrupted? + screen_goto=last; //"send message" to main loop, to switch the screen return true; } - - - - - - - diff --git a/common/gui/screen.h b/common/gui/screen.h index a1e8a32..fdef182 100644 --- a/common/gui/screen.h +++ b/common/gui/screen.h @@ -1,6 +1,9 @@ #ifndef SCREEN_H #define SCREEN_H +#include +#include + /** * @addtogroup gui */ @@ -8,38 +11,65 @@ /** * @defgroup screen Screen - * The Screen Submodule + * The Screen Submodule provides an api to navigate between different "screens" on the UI. + * Each screen must provide an enter, update and a leave method; which will be called from this module at the right time. + * The implemented screens of the application are documented in the \ref screens module. + */ +/*@}*/ + +/** + * @addtogroup screen */ /*@{*/ -#include -#include - -typedef void (*SCREEN_CALLBACK)(void* screen); //!< Function pointer used... +/** + * Prototype for Event Listeners (called when the screen is entered, left or should be updated) + * @param screen The pointer to the SCREEN_STRUCT where the event occurred + */ +typedef void (*SCREEN_CALLBACK)(void* screen); +/** + * Structure to configure the Screen + */ typedef struct SCREEN_S{ - SCREEN_CALLBACK on_enter; - SCREEN_CALLBACK on_leave; - SCREEN_CALLBACK on_update; - - - struct SCREEN_S* next; //Used internally. do not modify + SCREEN_CALLBACK on_enter; //!< The Callback which is called when the screen is entered. Add/Register all UI-Elements here + SCREEN_CALLBACK on_leave; //!< The Callback which is called when the screen is left. Remove/Unregister all UI-Elements here + SCREEN_CALLBACK on_update; //!< The Callback which is called repeatedly when the screen should be updated. Update/Redraw all UI-Elements here + struct SCREEN_S* next; //!< Used internally. do not modify, do not initialize } SCREEN_STRUCT; -//Navigate to the given string as soon as the app enters the main loop again. Method can be called from an interrupt + +/** + * Navigate to the given screen as soon as the app enters the main loop again (and gui_screen_update() is called) + * It's safe to call this method from an interrupt + * @param screen A Pointer to the preinitialized SCREEN_STRUCT + * @return true on success + */ bool gui_screen_navigate(SCREEN_STRUCT* screen); -//Navigate one screen back as soon as the app enters the main loop again. Method can be called from an interrupt +/** + * Navigate one screen back as soon as the app enters the main loop again. + * It's safe to call this method from an interrupt + * @return true on success + */ bool gui_screen_back(); -//Returns the current active screen + +/** + * Returns the currently active screen + * @return A Pointer to the active SCREEN_STRUCT + */ SCREEN_STRUCT* gui_screen_get_current(); //Updates/switches the screens. Call this from the app main loop, as fast as you can. +/** + * Updates the current screen. Switches the screen if gui_screen_navigate() or gui_screen_back() have been called since the last call to this method. + * This method should be called repeatedly from the main loop (e.g. app_process()) + */ void gui_screen_update(); -/*@}@}*/ +/*@}*/ #endif /* SCREEN_H */ diff --git a/common/lowlevel/ll_filesystem.h b/common/lowlevel/ll_filesystem.h index 6190598..8833b1f 100644 --- a/common/lowlevel/ll_filesystem.h +++ b/common/lowlevel/ll_filesystem.h @@ -1,5 +1,22 @@ #include "filesystem.h" +/** + * @addtogroup lowlevel + */ +/*@{*/ + +/** + * @defgroup ll_filesystem Filesystem (LowLevel) + * Low level functions for the \ref filesystem module + */ +/*@}*/ + +/** + * @addtogroup ll_filesystem + */ +/*@{*/ + + bool ll_filesystem_init(); DIRECTORY_STRUCT* ll_filesystem_dir_open(const char* path); @@ -9,3 +26,5 @@ void ll_filesystem_file_close(FILE_HANDLE* handle); FILE_STATUS ll_filesystem_file_seek(FILE_HANDLE* handle, uint32_t offset); FILE_STATUS ll_filesystem_file_read(FILE_HANDLE* handle, uint8_t* buf, uint32_t size); FILE_STATUS ll_filesystem_file_write(FILE_HANDLE* handle, uint8_t* buf, uint32_t size); + +/*@}*/ diff --git a/common/lowlevel/ll_system.h b/common/lowlevel/ll_system.h index b5aa6b0..be422fb 100644 --- a/common/lowlevel/ll_system.h +++ b/common/lowlevel/ll_system.h @@ -1,7 +1,29 @@ #include #include + +/** + * @defgroup lowlevel LowLevel + * The Low-Level platform abstraction layer + */ +/*@{*/ + +/** + * @defgroup ll_system System (LowLevel) + * Low level functions of the \ref system Module + */ + +/*@}*/ + +/** + * @addtogroup ll_system + */ +/*@{*/ + + bool ll_system_init(); void ll_system_delay(uint32_t msec); void ll_system_process(); void ll_system_toggle_led(); + +/*@}*/ diff --git a/common/lowlevel/ll_tft.h b/common/lowlevel/ll_tft.h index 11e3b1e..d7ac730 100644 --- a/common/lowlevel/ll_tft.h +++ b/common/lowlevel/ll_tft.h @@ -1,6 +1,23 @@ #include #include + +/** + * @addtogroup lowlevel + */ +/*@{*/ + +/** + * @defgroup ll_tft TFT (LowLevel) + * Low level functions for the \ref tft module + */ +/*@}*/ + +/** + * @addtogroup ll_tft + */ +/*@{*/ + // init functions bool ll_tft_init(); @@ -19,6 +36,6 @@ uint8_t ll_tft_font_height(uint8_t fontnum); uint8_t ll_tft_font_width(uint8_t fontnum); void ll_tft_draw_char(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, char c); - +/*@}*/ diff --git a/common/lowlevel/ll_touch.h b/common/lowlevel/ll_touch.h index c573a8e..c61b42e 100644 --- a/common/lowlevel/ll_touch.h +++ b/common/lowlevel/ll_touch.h @@ -1,6 +1,23 @@ #include #include +/** + * @addtogroup lowlevel + */ +/*@{*/ + +/** + * @defgroup ll_touch Touch (LowLevel) + * Low level functions for the \ref touch module + */ +/*@}*/ + +/** + * @addtogroup ll_touch + */ +/*@{*/ + + bool ll_touch_init(); - +/*@}*/ diff --git a/common/pixy/pixy.h b/common/pixy/pixy.h index f723afb..1709e33 100644 --- a/common/pixy/pixy.h +++ b/common/pixy/pixy.h @@ -17,7 +17,6 @@ #define __PIXY_H__ #include -#include #include "pixydefs.h" // Pixy C API // @@ -27,6 +26,13 @@ extern "C" { #endif + /** + * @defgroup pixy Pixy + * The Pixy Module + */ + /*@{*/ + + #define PIXY_MAX_SIGNATURE 7 // Pixy x-y position values @@ -164,7 +170,7 @@ extern "C" /** @brief Enable or disable pixy camera auto white balance. - @param enable 1: Enable white balance. + @param value 1: Enable white balance. 0: Disable white balance. @return 0 Success @return Negative Error @@ -220,7 +226,7 @@ extern "C" @return 0 Success @return Negative Error */ - int pixy_cam_set_exposure_compensation(uint8_t gain, uint16_t compensation); + int pixy_cam_set_exposure_compensation(uint8_t gain, uint16_t comp); /** @brief Get pixy camera exposure compensation. @@ -229,7 +235,7 @@ extern "C" @return 0 Success @return Negative Error */ - int pixy_cam_get_exposure_compensation(uint8_t * gain, uint16_t * compensation); + int pixy_cam_get_exposure_compensation(uint8_t * gain, uint16_t * comp); /** @brief Set pixy camera brightness. @@ -279,6 +285,8 @@ extern "C" */ int pixy_get_firmware_version(uint16_t * major, uint16_t * minor, uint16_t * build); + /*@}*/ + #ifdef __cplusplus } #endif diff --git a/common/system/system.h b/common/system/system.h index 31f7bc9..7c22a68 100644 --- a/common/system/system.h +++ b/common/system/system.h @@ -1,6 +1,9 @@ #ifndef SYSTEM_H #define SYSTEM_H +#include +#include + /** * @defgroup system System * The System Module provides access to delay functions, leds and provides a system init function @@ -8,9 +11,6 @@ /*@{*/ -#include -#include - /** * Initializes the system. Call this method at the start of your app_init() function and before using any system_* functions * @return true on success diff --git a/common/tft/tft.c b/common/tft/tft.c index 2395668..0b5679a 100644 --- a/common/tft/tft.c +++ b/common/tft/tft.c @@ -3,6 +3,17 @@ #include #include #include +#include "filesystem.h" + +/* The idea is as follows: + * Most of the tft_* functions can be forwarded to the lowlevel implementation. + * The exceptions are commented below. + * Make sure to have a look at the doxygen comments for the lowlevel functions and for the tft_* functions + */ + +/* Possible improvements: + * For formatted printing implement putchar, instead of writing into a buffer and drawing that buffer afterwards + */ bool tft_init() { return ll_tft_init(); @@ -51,19 +62,82 @@ uint8_t tft_font_width(uint8_t fontnum) { return ll_tft_font_width(fontnum); } +//Print line can be done with multiple calls to draw_char void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* text) { - if(font>=ll_tft_num_fonts()) return; - for(int i=0; i=ll_tft_num_fonts()) return; //invalid font index + for(int i=0; i +#include + /** * @defgroup tft TFT * The TFT Modul provides access to the display @@ -13,9 +16,6 @@ /*@{*/ -#include -#include - /** * Creates a 16bit color from 8bit * 3 colors (r,g,b) * @return @@ -67,7 +67,7 @@ void tft_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t * Draws a pixel onto the display. * @param x The x-Coordinate of the pixel * @param y The y-Coordinate of the pixel - * @param The 16-bit color to draw the pixel with + * @param color The 16-bit color to draw the pixel with */ void tft_draw_pixel(uint16_t x,uint16_t y,uint16_t color); @@ -78,7 +78,7 @@ void tft_draw_pixel(uint16_t x,uint16_t y,uint16_t color); * @param y1 The y-Coordinate of the start-point * @param x2 The x-Coordinate of the end-point * @param y2 The y-Coordinate of the end-point - * @param The 16-bit color to draw the pixel with + * @param color The 16-bit color to draw the pixel with */ void tft_draw_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_t color); @@ -88,7 +88,7 @@ void tft_draw_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_ * @param y1 The y-Coordinate of the start-point * @param x2 The x-Coordinate of the end-point * @param y2 The y-Coordinate of the end-point - * @param The 16-bit color to draw the pixel with + * @param color The 16-bit color to draw the pixel with */ void tft_fill_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_t color); @@ -103,6 +103,18 @@ void tft_fill_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_ */ void tft_draw_bitmap_unscaled(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint16_t* dat); + +/** + * Draws a bitmap from the filesystem onto the display without scaling/cropping + * The bitmap must be saved in the windows bitmap format (.bmp) without compression and with 24 (b,g,r) or 32 (a,r,g,b) bits per pixel + * @param x The x-coordinate of the top-left corner to draw the bitmap at + * @param y The y-coordinate of the top-left corner to draw the bitmap at + * @param filename The absolute path to the .bmp file + * @return true on success + */ +bool tft_draw_bitmap_file_unscaled(uint16_t x, uint16_t y, const char* filename); + + /** * Draws the outline of a circle onto the display * @param x The x-Coordinate of the center point @@ -151,6 +163,7 @@ void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, ui * @param bgcolor The 16-bit background color of the text. You may pass TRANSPARENT as Color * @param font The Fontnum to use for drawing * @param format The format string (like printf) + * @param ... The arguments to format (like printf) */ void tft_print_formatted(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* format, ...); diff --git a/common/touch/touch.c b/common/touch/touch.c index ad8c589..abd85b0 100644 --- a/common/touch/touch.c +++ b/common/touch/touch.c @@ -2,11 +2,23 @@ #include "ll_touch.h" #include -#define NUM_AREAS 50 //Number of Structs Reserved in Memory for TouchAreas (e.g Buttons) -TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL}; +/* The idea is as follows: + * The user can add "touch-areas" which basically represent a rectangles on the screen. + * Once the user touches such a rectangle with the pen, we forward events to his provided callback. + * Touch events are provided to us from the low level implementation via touch_add_raw_event(). + * We then need to check which touch areas are effected by that event + */ -volatile POINT_STRUCT pos; -volatile TOUCH_STATE oldState=TOUCH_UP; +/* Possible improvements: + * Exchange pointer-list "areas" with a linked list. This would ensure that we can always accept new regions + * Implement calibration stuff, and calculate the real coordinates out of the data provided in touch_add_raw_event() + */ + +#define NUM_AREAS 50 //Number of Touch Areas we can manage +TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL}; //list with pointers to all managed touch area's + +volatile POINT_STRUCT pos; //the last touch point +volatile TOUCH_STATE oldState=TOUCH_UP; //the last touch state bool touch_init() { return ll_touch_init(); @@ -14,117 +26,124 @@ bool touch_init() { bool touch_add_raw_event(uint16_t touchX, uint16_t touchY, TOUCH_STATE state) { - bool penDown = (state==TOUCH_DOWN); - bool oldPenDown = (oldState==TOUCH_DOWN); - oldState=state; + //Update current and old position/state + bool penDown = (state==TOUCH_DOWN); + bool oldPenDown = (oldState==TOUCH_DOWN); + oldState=state; pos.x=touchX; pos.y=touchY; - if(penDown) - { - // tftDrawPixel(touchX,touchY,WHITE); - if(!oldPenDown) //First Touch - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) - { - areas[z]->flags=1; //PenInside=1 - if(areas[z]->hookedActions & PEN_DOWN) - areas[z]->callback(areas[z],PEN_DOWN); - } - } - } - else //Second, Third - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL ) - { - if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) - { - if(areas[z]->flags==0) //PenInside ==0 - { - areas[z]->flags=1; //PenInside=1 - if(areas[z]->hookedActions & PEN_ENTER) - areas[z]->callback(areas[z],PEN_ENTER); - } - } - else if(areas[z]->flags) //PenInside==1 - { - areas[z]->flags=0; //PenInside=0 - if(areas[z]->hookedActions & PEN_LEAVE) - areas[z]->callback(areas[z],PEN_LEAVE); - } - } - } - } - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && areas[z]->hookedActions&PEN_MOVE) - { - if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) - { - areas[z]->callback(areas[z],PEN_MOVE); - } - } - } - } - else - { - if(oldPenDown) //Was the pen ever down (or was it a too short touch) - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) - { - areas[z]->flags=0; //PenInside = 0; - if(areas[z]->hookedActions & PEN_UP) - areas[z]->callback(areas[z],PEN_UP); - } - } - } - touchX=0xFFFF; - touchY=0xFFFF; - } + if(penDown) //pen is down now + { + //tft_draw_pixel(touchX,touchY,WHITE); + if(!oldPenDown) //pen wasn't down before (positive edge) => First Touch + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + //Check if pos is inside area + if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) + { + areas[z]->flags=1; //Save PenInside=1 + if(areas[z]->hookedActions & PEN_DOWN) //The user wants to receive pen down events + areas[z]->callback(areas[z],PEN_DOWN); //Send event to user callback + } + } + } + else //Pen was down before => Second, Third event in row + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + if(areas[z]!=NULL ) + { + //Check if pos is inside area + if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) + { + if(areas[z]->flags==0) //Pen was not inside before (PenInside==0) + { + areas[z]->flags=1; //Pen is inside now (PenInside=1) + if(areas[z]->hookedActions & PEN_ENTER) //The user wants to receive pen enter events + areas[z]->callback(areas[z],PEN_ENTER); + } + } + else if(areas[z]->flags) //Pos not inside area, but it was before (PenInside==1) + { + areas[z]->flags=0; //Pen is no longer inside (PenInside=0) + if(areas[z]->hookedActions & PEN_LEAVE) //The user wants to receive pen leave events + areas[z]->callback(areas[z],PEN_LEAVE); + } + } + } + } + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + if(areas[z]!=NULL && (areas[z]->hookedActions&PEN_MOVE)) //User want's to receive pen move events + { + //Check if pos is inside area + if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) + { + areas[z]->callback(areas[z],PEN_MOVE); + } + } + } + } + else //pen is not down now + { + if(oldPenDown) //but it was down before (negative edge) + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + //Check if pos is inside area + if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) + { + areas[z]->flags=0; //The pen is no longer inside (PenInside = 0); + if(areas[z]->hookedActions & PEN_UP) //user want's to receive pen up events + areas[z]->callback(areas[z],PEN_UP); + } + } + } + } return true; } bool touch_have_empty(unsigned char num) { - for(unsigned char i=0; iChange NUM_AREAS). -{ - - for(unsigned char i=0; iflags=0; - areas[i]=area; - return true; - } - } - return false; + //go through pointer array and check for free spaces + for(unsigned char i=0; iflags=0; //we start with empty flags (PenInside=0) + areas[i]=area; //save pointer into list + return true; + } + } + return false; //no free space found +} + +void touch_unregister_area(TOUCH_AREA_STRUCT* area) +{ + if(area==NULL) return; + + //go through pointer array and find the area to remove + for(unsigned char i=0; i +#include + /** * @defgroup touch Touch * The Touch module provides access to the touch controller, and executes a callback if a certain region is touched @@ -12,10 +15,6 @@ */ /*@{*/ - -#include -#include - /** Enum to describe the current Touch State. \sa touch_add_raw_event */ @@ -39,6 +38,7 @@ typedef enum { /** * Prototype for Event Listeners (called for every occurring, hooked action) + * \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance. * @param touchArea The pointer to the TOUCH_AREA_STRUCT in which the event occurred * @param triggeredAction The Action which occurred */ @@ -53,8 +53,8 @@ typedef struct { uint16_t y1; //!< Top Left Y-Coordinate of Area uint16_t x2; //!< Bottom Right X-Coordinate of Area uint16_t y2; //!< Bottom Right Y-Coordinate of Area - TOUCH_CALLBACK callback; //!< Callback - uint8_t flags; //!< For internal Used, don't change, don't initialize + TOUCH_CALLBACK callback; //!< Callback which is executed when an event occurred in this Area. + uint8_t flags; //!< For internal use, don't change, don't initialize } TOUCH_AREA_STRUCT; diff --git a/doc/docu.odt b/doc/docu.odt index 5915516..5d3bd2a 100644 Binary files a/doc/docu.odt and b/doc/docu.odt differ diff --git a/doc/docu.pdf b/doc/docu.pdf index 78d7786..4894e60 100644 Binary files a/doc/docu.pdf and b/doc/docu.pdf differ diff --git a/doc/inheritance.dia b/doc/inheritance.dia new file mode 100644 index 0000000..96c4483 Binary files /dev/null and b/doc/inheritance.dia differ diff --git a/doc/inheritance.png b/doc/inheritance.png new file mode 100644 index 0000000..94fc3ed Binary files /dev/null and b/doc/inheritance.png differ diff --git a/emulator/emulated/pixy_small.bmp b/emulator/emulated/pixy_small.bmp new file mode 100644 index 0000000..39ad349 Binary files /dev/null and b/emulator/emulated/pixy_small.bmp differ diff --git a/emulator/emulated/shot1.bmp b/emulator/emulated/shot1.bmp new file mode 100644 index 0000000..f39b294 Binary files /dev/null and b/emulator/emulated/shot1.bmp differ diff --git a/emulator/emulated/stm_small.bmp b/emulator/emulated/stm_small.bmp new file mode 100644 index 0000000..00f39d7 Binary files /dev/null and b/emulator/emulated/stm_small.bmp differ diff --git a/emulator/emulated/test2.txt b/emulator/emulated/test2.txt deleted file mode 100644 index aaaa72b..0000000 --- a/emulator/emulated/test2.txt +++ /dev/null @@ -1 +0,0 @@ -hallo welt diff --git a/emulator/qt/ll_tft.cpp b/emulator/qt/ll_tft.cpp index 6a9ad0c..ced8b52 100644 --- a/emulator/qt/ll_tft.cpp +++ b/emulator/qt/ll_tft.cpp @@ -44,13 +44,15 @@ void ll_tft_draw_circle(uint16_t x, uint16_t y, uint16_t r, uint16_t color) { } uint8_t ll_tft_num_fonts() { - return 1; + return 2; } QFont get_font(uint8_t fontnum) { switch(fontnum) { case 0: return QFont("Monospace",8); + case 1: + return QFont("DejaVu Sans Mono",14); default: return QFont(); } diff --git a/emulator/qt/mainwindow.cpp b/emulator/qt/mainwindow.cpp index 3756730..80341f3 100644 --- a/emulator/qt/mainwindow.cpp +++ b/emulator/qt/mainwindow.cpp @@ -34,6 +34,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), image(DISPLAY_WID ui->setupUi(this); image.fill(Qt::black); currentScale = 1; + ui->widgetDisplay->setMouseTracking(true); + ui->widgetDisplay->installEventFilter(this); } void MainWindow::draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) @@ -159,7 +161,28 @@ void MainWindow::mouseMoveEvent(QMouseEvent *evt) { //qDebug() << "move" << evt->pos(); checkAndSendEvent(evt->pos(),true); +} + +bool MainWindow::eventFilter(QObject *obj, QEvent *evt) +{ + if(obj==ui->widgetDisplay) { + switch(evt->type()) { + case QEvent::MouseMove: + { + QMouseEvent* mouseEvent = static_cast(evt); + QPoint p = (mouseEvent->pos()-QPoint(1,1))/currentScale; + if(p.x()txtMousePos->setText(QString("Mouse Position: (%1,%2)").arg(p.x()).arg(p.y())); + } + } + break; + + default: break; + } + } + + return false; } diff --git a/emulator/qt/mainwindow.h b/emulator/qt/mainwindow.h index ad14809..e9034e1 100644 --- a/emulator/qt/mainwindow.h +++ b/emulator/qt/mainwindow.h @@ -25,6 +25,7 @@ public: void draw_char(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, QFont font, char c); protected: + bool eventFilter(QObject * obj , QEvent * env); void paintEvent(QPaintEvent * evt); void mousePressEvent(QMouseEvent* evt); void mouseReleaseEvent(QMouseEvent* evt); diff --git a/emulator/qt/mainwindow.ui b/emulator/qt/mainwindow.ui index 293d0a3..8fca011 100644 --- a/emulator/qt/mainwindow.ui +++ b/emulator/qt/mainwindow.ui @@ -43,6 +43,13 @@ + + + + Mouse Position: out of bounds + + +