diff --git a/Makefile b/Makefile index c9ac771..627c23e 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,8 @@ CSRC = $(STARTUPSRC) \ $(PLATFORMSRC) \ $(BOARDSRC) \ $(STREAMSSRC) \ + FatFs/ff.c \ + FatFs/ffunicode.c \ usbcfg.c \ main.c si5351.c tlv320aic3204.c dsp.c plot.c ui.c ili9341.c numfont20x22.c Font5x7.c flash.c adc.c rtc.c @@ -209,7 +211,7 @@ UDEFS = -DSHELL_CMD_TEST_ENABLED=FALSE -DSHELL_CMD_MEM_ENABLED=FALSE -DARM_MATH_ #UDEFS+= -DVNA_USE_LSE # Define ASM defines here -UADEFS = +UADEFS = # List all user directories here UINCDIR = diff --git a/nanovna.h b/nanovna.h index 0b86881..5e07ce1 100644 --- a/nanovna.h +++ b/nanovna.h @@ -23,6 +23,8 @@ #define __USE_DISPLAY_DMA__ // Add RTC clock support //#define __USE_RTC__ +// Add SD card support, req enable RTC (additional settings for file system see FatFS lib ffconf.h) +//#define __USE_SD_CARD__ /* * main.c @@ -100,6 +102,8 @@ enum stimulus_type { ST_START=0, ST_STOP, ST_CENTER, ST_SPAN, ST_CW }; +int shell_printf(const char *fmt, ...); + void set_sweep_frequency(int type, uint32_t frequency); uint32_t get_sweep_frequency(int type); uint32_t get_bandwidth_frequency(void); @@ -407,6 +411,13 @@ void ili9341_line(int x0, int y0, int x1, int y1); void show_version(void); void show_logo(void); +// SD Card support, discio functions for FatFS lib implemented in ili9341.c +#ifdef __USE_SD_CARD__ +#include "fatfs\ff.h" +#include "fatfs\diskio.h" +void testLog(void); // debug log +#endif + /* * rtc.c */ diff --git a/ui.c b/ui.c index cd60f74..64f7aee 100644 --- a/ui.c +++ b/ui.c @@ -62,6 +62,19 @@ volatile uint8_t operation_requested = OP_NONE; int8_t previous_marker = -1; +#ifdef __USE_SD_CARD__ +#if SPI_BUFFER_SIZE < 2048 +#error "SPI_BUFFER_SIZE for SD card support need size = 2048" +#else +// Fat file system work area (at the end of spi_buffer) +static FATFS *fs_volume = (FATFS *)(((uint8_t*)(&spi_buffer[SPI_BUFFER_SIZE])) - sizeof(FATFS)); +// FatFS file object (at the end of spi_buffer) +static FIL *fs_file = ( FIL*)(((uint8_t*)(&spi_buffer[SPI_BUFFER_SIZE])) - sizeof(FATFS) - sizeof(FIL)); +// Filename object (at the end of spi_buffer) +static char *fs_filename = ( char*)(((uint8_t*)(&spi_buffer[SPI_BUFFER_SIZE])) - sizeof(FATFS) - sizeof(FIL) - FF_LFN_BUF - 4); +#endif +#endif + enum { UI_NORMAL, UI_MENU, UI_NUMERIC, UI_KEYPAD }; @@ -862,6 +875,99 @@ menu_marker_sel_cb(int item, uint8_t data) draw_menu(); } +#ifdef __USE_SD_CARD__ +#define SAVE_S1P_FILE 1 +#define SAVE_S2P_FILE 2 + +static const char s1_file_header[] = + "!File created by NanoVNA\r\n"\ + "# HZ S RI R 50\r\n"; + +static const char s1_file_param[] = + "%10d % f % f\r\n"; + +static const char s2_file_header[] = + "!File created by NanoVNA\r\n"\ + "# HZ S RI R 50\r\n"; + +static const char s2_file_param[] = + "%10d % f % f % f % f 0 0 0 0\r\n"; + +static void +menu_sdcard_cb(int item, uint8_t data) +{ + (void)item; + char *buf = (char *)spi_buffer; +// shell_printf("S file\r\n"); + FRESULT res = f_mount(fs_volume, "", 1); +// shell_printf("Mount = %d\r\n", res); + if (res != FR_OK) + return; + // Prepare filename = .s1p or .s2p and open for write +#if FF_USE_LFN >= 1 + uint32_t tr = rtc_get_tr_bcd(); // TR read first + uint32_t dr = rtc_get_dr_bcd(); // DR read second + plot_printf(fs_filename, FF_LFN_BUF, "VNA_%06X_%06X.s%dp", dr, tr, data); +#else + plot_printf(fs_filename, FF_LFN_BUF, "%08X.s%dp", rtc_get_FAT(), data); +#endif + + int i; + UINT size; +// UINT total_size = 0; +// systime_t time = chVTGetSystemTimeX(); + res = f_open(fs_file, fs_filename, FA_CREATE_ALWAYS | FA_READ | FA_WRITE); +// shell_printf("Open %s, = %d\r\n", fs_filename, res); + if (res == FR_OK){ + // Write S1P file + if (data == SAVE_S1P_FILE){ + // write s1p header (not write NULL terminate at end) + res = f_write(fs_file, s1_file_header, sizeof(s1_file_header)-1, &size); +// total_size+=size; + // Write all points data + for (i = 0; i < sweep_points && res == FR_OK; i++) { + size = plot_printf(buf, 128, s1_file_param, frequencies[i], measured[0][i][0], measured[0][i][1]); +// total_size+=size; + res = f_write(fs_file, buf, size, &size); + } + } + // Write S2P file + else if (data == SAVE_S2P_FILE){ + // Write s2p header (not write NULL terminate at end) + res = f_write(fs_file, s2_file_header, sizeof(s2_file_header)-1, &size); +// total_size+=size; + // Write all points data + for (i = 0; i < sweep_points && res == FR_OK; i++) { + size = plot_printf(buf, 128, s2_file_param, frequencies[i], measured[0][i][0], measured[0][i][1], measured[1][i][0], measured[1][i][1]); +// total_size+=size; + res = f_write(fs_file, buf, size, &size); + } + } + res = f_close(fs_file); +// shell_printf("Close = %d\r\n", res); +// testLog(); +// time = chVTGetSystemTimeX() - time; +// shell_printf("Total time: %dms (write %d byte/sec)\r\n", time/10, total_size*10000/time); + } + + ili9341_fill(LCD_WIDTH/2-96, LCD_HEIGHT/2-30, 96*2, 60, config.menu_normal_color); + ili9341_set_foreground(DEFAULT_MENU_TEXT_COLOR); + ili9341_set_background(config.menu_normal_color); + ili9341_drawstring("SAVE TRACE", LCD_WIDTH/2-5*FONT_WIDTH, LCD_HEIGHT/2-20); + ili9341_drawstring(res == FR_OK ? fs_filename : " Fail write ", LCD_WIDTH/2-76, LCD_HEIGHT/2); + chThdSleepMilliseconds(2000); + request_to_redraw_grid(); + ui_mode_normal(); +} + +static const menuitem_t menu_sdcard[] = { + { MT_CALLBACK, SAVE_S1P_FILE, "SAVE S1P", menu_sdcard_cb }, + { MT_CALLBACK, SAVE_S2P_FILE, "SAVE S2P", menu_sdcard_cb }, + { MT_CANCEL, 0, S_LARROW" BACK", NULL }, + { MT_NONE, 0, NULL, NULL } // sentinel +}; +#endif + static const menuitem_t menu_calop[] = { { MT_CALLBACK, CAL_OPEN, "OPEN", menu_calop_cb }, { MT_CALLBACK, CAL_SHORT, "SHORT", menu_calop_cb }, @@ -1084,6 +1190,9 @@ const menuitem_t menu_top[] = { { MT_SUBMENU, 0, "STIMULUS", menu_stimulus }, { MT_SUBMENU, 0, "CAL", menu_cal }, { MT_SUBMENU, 0, "RECALL", menu_recall }, +#ifdef __USE_SD_CARD__ + { MT_SUBMENU, 0, "SD CARD", menu_sdcard }, +#endif { MT_SUBMENU, 0, "CONFIG", menu_config }, { MT_NONE, 0, NULL, NULL } // sentinel }; @@ -2176,6 +2285,86 @@ touch_pickup_marker(int touch_x, int touch_y) return FALSE; } +#ifdef __USE_SD_CARD__ +//***************************************************************************** +// Bitmap file header for 320x240 image 16bpp (v4 format allow set RGB mask) +//***************************************************************************** +static const uint8_t bmp_header_v4[14+56] = { +// BITMAPFILEHEADER (14 byte size) + 0x42, 0x4D, // BM signature + 0x46, 0x58, 0x02, 0x00, // File size = 320*240*2 + 14 + 56 = 0x00025846 + 0x00, 0x00, // reserved + 0x00, 0x00, // reserved + 0x46, 0x00, 0x00, 0x00, // Size of all headers = 14+56 +// BITMAPINFOv4 (56 byte size) + 0x38, 0x00, 0x00, 0x00, // Data offset after this point (56 = 0x38) + 0x40, 0x01, 0x00, 0x00, // Width = 320 = 0x00000140 + 0xF0, 0x00, 0x00, 0x00, // Height = 240 = 0x000000F0 + 0x01, 0x00, // Planes + 0x10, 0x00, // 16bpp + 0x03, 0x00, 0x00, 0x00, // Compression (BI_BITFIELDS) + 0x00, 0x58, 0x02, 0x00, // Bitmap size = 320*240*2 = 0x00025800 + 0xC4, 0x0E, 0x00, 0x00, // x Resolution (96 DPI = 96 * 39.3701 inches per metre = 0x0EC4) + 0xC4, 0x0E, 0x00, 0x00, // y Resolution (96 DPI = 96 * 39.3701 inches per metre = 0x0EC4) + 0x00, 0x00, 0x00, 0x00, // Palette size + 0x00, 0x00, 0x00, 0x00, // Palette used +// Extend v4 header data (color mask for RGB565) + 0x00, 0xF8, 0x00, 0x00, // R mask = 0b11111000 00000000 + 0xE0, 0x07, 0x00, 0x00, // G mask = 0b00000111 11100000 + 0x1F, 0x00, 0x00, 0x00, // B mask = 0b00000000 00011111 + 0x00, 0x00, 0x00, 0x00 // A mask = 0b00000000 00000000 +}; + +static int +made_screenshot(int touch_x, int touch_y) +{ + int y, i; + UINT size; + if (touch_y < HEIGHT || touch_x < FREQUENCIES_XPOS3 || touch_x > FREQUENCIES_XPOS2) + return FALSE; + touch_wait_release(); +// uint32_t time = chVTGetSystemTimeX(); +// shell_printf("Screenshot\r\n"); + FRESULT res = f_mount(fs_volume, "", 1); + // fs_volume, fs_file and fs_filename stored at end of spi_buffer!!!!! + uint16_t *buf = spi_buffer; +// shell_printf("Mount = %d\r\n", res); + if (res != FR_OK) + return TRUE; +#if FF_USE_LFN >= 1 + uint32_t tr = rtc_get_tr_bcd(); // TR read first + uint32_t dr = rtc_get_dr_bcd(); // DR read second + plot_printf(fs_filename, FF_LFN_BUF, "VNA_%06X_%06X.bmp", dr, tr); +#else + plot_printf(fs_filename, FF_LFN_BUF, "%08X.bmp", rtc_get_FAT()); +#endif + res = f_open(fs_file, fs_filename, FA_CREATE_ALWAYS | FA_READ | FA_WRITE); +// shell_printf("Open %s, result = %d\r\n", fs_filename, res); + if (res == FR_OK){ + res = f_write(fs_file, bmp_header_v4, sizeof(bmp_header_v4), &size); + for (y = LCD_HEIGHT-1; y >= 0 && res == FR_OK; y--) { + ili9341_read_memory(0, y, LCD_WIDTH, 1, LCD_WIDTH, buf); + for (i = 0; i < LCD_WIDTH; i++) + buf[i] = __REVSH(buf[i]); // swap byte order (example 0x10FF to 0xFF10) + res = f_write(fs_file, buf, LCD_WIDTH*sizeof(uint16_t), &size); + } + res = f_close(fs_file); +// shell_printf("Close %d\r\n", res); +// testLog(); + } +// time = chVTGetSystemTimeX() - time; +// shell_printf("Total time: %dms (write %d byte/sec)\r\n", time/10, (LCD_WIDTH*LCD_HEIGHT*sizeof(uint16_t)+sizeof(bmp_header_v4))*10000/time); + ili9341_fill(LCD_WIDTH/2-96, LCD_HEIGHT/2-30, 96*2, 60, config.menu_normal_color); + ili9341_set_foreground(DEFAULT_MENU_TEXT_COLOR); + ili9341_set_background(config.menu_normal_color); + ili9341_drawstring("SCREENSHOT", LCD_WIDTH/2-5*FONT_WIDTH, LCD_HEIGHT/2-20); + ili9341_drawstring(res == FR_OK ? fs_filename : " Fail write ", LCD_WIDTH/2-76, LCD_HEIGHT/2); + request_to_redraw_grid(); + chThdSleepMilliseconds(2000); + return TRUE; +} +#endif + static int touch_lever_mode_select(int touch_x, int touch_y) { @@ -2208,6 +2397,10 @@ void ui_process_touch(void) // Try drag marker if (touch_pickup_marker(touch_x, touch_y)) break; +#ifdef __USE_SD_CARD__ + if (made_screenshot(touch_x, touch_y)) + break; +#endif // Try select lever mode (top and bottom screen) if (touch_lever_mode_select(touch_x, touch_y)) { touch_wait_release();