SD card: don't read into a buffer, parse directly instead.

Formerly we took efforts to read only small chunks into a
(small) buffer, just to read this buffer byte by byte yet
again for parsing. It's more efficient and requires less
code to parse the character at read time directly. This
way we can read in chunks of exactly one line, making the
buffer obsolete.

First step is to implement this in mendel.c and in sd.c/.h.
This gets rid of the buffer already.

Very inefficient in pff.c and pff_diskio.c so far, more
than 40 minutes / less than 500 bytes/s for reading this
1 MB comments file. Reason is, for every single byte a
whole sector is read. Nevertheless, this attempt appears
to be on the right track.

Binary is 156 bytes smaller, 16 bytes less RAM:
               ATmega...     '168   '328(P)   '644(P)     '1280
   FLASH:   22052 bytes   153.82%    71.78%    34.73%    17.09%
     RAM:    1331 bytes   129.98%    64.99%    32.50%    16.25%
  EEPROM:      32 bytes     3.12%     1.56%     1.56%     0.78%
This commit is contained in:
Markus Hitter 2015-07-08 23:36:28 +02:00
parent 3520f54db7
commit 75df5e3a0a
5 changed files with 81 additions and 41 deletions

View File

@ -287,19 +287,12 @@ int main (void)
#ifdef SD
if (( ! gcode_active || gcode_active & GCODE_SOURCE_SD) &&
gcode_sources & GCODE_SOURCE_SD) {
c = sd_read_char();
if (c == 0) {
if (sd_read_gcode_line()) {
serial_writestr_P(PSTR("\nSD file done.\n"));
gcode_sources &= ! GCODE_SOURCE_SD;
// There is no pf_close(), subsequent reads will stick at EOF
// and return zeros.
}
else {
gcode_active = GCODE_SOURCE_SD;
line_done = gcode_parse_char(c);
if (line_done)
gcode_active = 0;
}
}
#endif

52
pff.c
View File

@ -951,6 +951,58 @@ FRESULT pf_read (
return FR_OK;
}
FRESULT pf_parse_line (
uint8_t (*parser)(uint8_t) /* Pointer to the parser function, which
should return 1 on EOL, else zero. */
)
{
BYTE one_byte_buffer;
DRESULT dr;
CLUST clst;
DWORD sect;
UINT rcnt;
BYTE cs;
FATFS *fs = FatFs;
if (!fs) return FR_NOT_ENABLED; /* Check file system */
if (!(fs->flag & FA_OPENED)) /* Check if opened */
return FR_NOT_OPENED;
while (fs->fptr < fs->fsize) { /* Repeat until EOF. */
if ((fs->fptr % 512) == 0) { /* On the sector boundary? */
cs = (BYTE)(fs->fptr / 512 & (fs->csize - 1)); /* Sector offset in the cluster */
if (!cs) { /* On the cluster boundary? */
if (fs->fptr == 0) /* On the top of the file? */
clst = fs->org_clust;
else
clst = get_fat(fs->curr_clust);
if (clst <= 1) ABORT(FR_DISK_ERR);
fs->curr_clust = clst; /* Update current cluster */
}
sect = clust2sect(fs->curr_clust); /* Get current sector */
if (!sect) ABORT(FR_DISK_ERR);
fs->dsect = sect + cs;
}
rcnt = 1;
// TODO: this is very inefficient, it reads a full sector for every
// single byte. Instead the parser function should be passed to
// disk_readp(), so it can read and parse the remaining sector
// in search for an EOL and pass back the number of bytes read.
// It should also pass back wether the line was completed or not.
// If yes, we're done; if no, we call again with the next sector.
dr = disk_readp(&one_byte_buffer, fs->dsect, (UINT)fs->fptr % 512, 1);
if (dr) ABORT(FR_DISK_ERR);
fs->fptr += rcnt; /* Update pointers and counters */
if (parser(one_byte_buffer))
return FR_OK;
}
return FR_END_OF_FILE;
}
#endif

4
pff.h
View File

@ -93,7 +93,8 @@ typedef enum {
FR_NO_FILE, /* 3 */
FR_NOT_OPENED, /* 4 */
FR_NOT_ENABLED, /* 5 */
FR_NO_FILESYSTEM /* 6 */
FR_NO_FILESYSTEM, /* 6 */
FR_END_OF_FILE, /* 7, used only by pf_read_gcode_line(). */
} FRESULT;
@ -105,6 +106,7 @@ FRESULT pf_mount (FATFS* fs); /* Mount a logical d
void pf_unmount (FATFS* fs); /* Unmount a logical drive */
FRESULT pf_open (const char* path); /* Open a file */
FRESULT pf_read (void* buff, UINT btr, UINT* br); /* Read data from the open file */
FRESULT pf_parse_line (uint8_t (*parser)(uint8_t)); /* Read and parse a line of data from the open file. */
FRESULT pf_write (const void* buff, UINT btw, UINT* bw); /* Write data to the open file */
FRESULT pf_lseek (DWORD ofs); /* Move file pointer of the open file */
FRESULT pf_opendir (DIR* dj, const char* path); /* Open a directory */

55
sd.c
View File

@ -9,16 +9,12 @@
#include "delay.h"
#include "serial.h"
#include "sersendf.h"
#define SD_BUFFER_SIZE 16
#include "gcode_parse.h"
static FATFS sdfile;
static FRESULT result;
static uint8_t sd_buffer[SD_BUFFER_SIZE];
static uint8_t sd_buffer_ptr = SD_BUFFER_SIZE;
/** Initialize SPI for SD card reading.
*/
void sd_init(void) {
@ -88,38 +84,35 @@ void sd_open(const char* filename) {
}
}
/** Read a character from a file.
/** Read a line of G-code from a file.
\return The character read, or zero if there is no such character (e.g. EOF).
\param A pointer to the parser function. This function should accept an
uint8_t with the character to parse and return an uint8_t wether
end of line (EOL) was reached.
In principle it'd be possible to read the file character by character.
However, Before too long this will cause the printer to read G-code from this file
until done or until stopped by G-code coming in over the serial line.
\return Wether end of line (EOF) was reached or an error happened.
Juggling with a buffer smaller than 512 bytes means that the underlying
SD card handling code reads a full sector (512 bytes) in each operation,
but throws away everything not fitting into this buffer. Next read operation
reads the very same sector, but keeps a different part. That's ineffective.
Much better is to parse straight as it comes from the card. This way there
is no need for a buffer at all. Sectors are still read multiple times, but
at least one line is read in one chunk (unless it crosses a sector boundary).
*/
uint8_t sd_read_char(void) {
UINT read;
uint8_t this_char;
uint8_t sd_read_gcode_line(void) {
if (sd_buffer_ptr == SD_BUFFER_SIZE) {
result = pf_read(sd_buffer, SD_BUFFER_SIZE, &read);
if (result != FR_OK) {
sersendf_P(PSTR("E: failed to read from file. (%su)"), result);
return 0;
}
if (read < SD_BUFFER_SIZE) {
sd_buffer[read] = 0; // A zero marks EOF.
}
sd_buffer_ptr = 0;
result = pf_parse_line(&gcode_parse_char);
if (result == FR_END_OF_FILE) {
return 1;
}
else if (result != FR_OK) {
sersendf_P(PSTR("E: failed to parse from file. (%su)"), result);
return 1;
}
this_char = sd_buffer[sd_buffer_ptr];
if (this_char == 0)
sd_buffer_ptr = SD_BUFFER_SIZE; // Start over, perhaps with next file.
else
sd_buffer_ptr++;
return this_char;
return 0;
}
#endif /* SD */

2
sd.h
View File

@ -25,7 +25,7 @@ void sd_list(const char* path);
void sd_open(const char* filename);
uint8_t sd_read_char(void);
uint8_t sd_read_gcode_line(void);
#endif /* SD_CARD_SELECT_PIN */