diff --git a/Makefile b/Makefile index 39272bb..b6cf60c 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ MAIN = avrtft.c SRC = bitmaps.c bmp.c cmd.c display.c emojis.c i2c.c paint.c tft.c touch.c \ - font.c spi.c hack.c usart.c + font.c spi.c hack.c usart.c sdcard.c CC = avr-gcc OBJCOPY = avr-objcopy @@ -55,7 +55,7 @@ $(TARGET).elf: bitmaps.h bmp.h cmd.h display.h emojis.h i2c.h paint.h tft.h \ touch.h font.h pins.h spi.h types.h hack.h usart.h utils.h \ - Makefile + sdcard.h Makefile all: $(TARGET).hex diff --git a/README.md b/README.md index a153e9c..4aeaef6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AVRTFT -Simple project to drive a TFT LCD with an AVR MCU (ATmega328P) and avr-libc. +Experimental project to drive a TFT LCD with an AVR MCU (ATmega328P) and avr-libc. Currently supported displays/drivers: @@ -17,19 +17,15 @@ * Draw bitmaps * Write text and bitmaps via USART * Upload BMP images via USART (16-Bit 5/6/5 RGB) +* Basic SD card support: read and write blocks of 512 bytes +* Read BMP images from SD card (raw) * Process touch events (FT6206) * Very basic paint application -* Logging via USART - -The AVR is clocked with a crystal for reliable communication via USART. - -RAM usage currently is 210 bytes. Program memory is fully used, mainly by -font and emoji bitmaps. +* Logging via USART Ideas: -* Read pictures from SD Card -* Display 4K@50Hz videos... +* Make features usable from a touch screen menu ![IMG_20231125_011054](https://github.com/gitdode/avrtft/assets/11530253/cd3d94eb-fb16-4d78-9eaa-bebfec8f8ef7) @@ -44,6 +40,7 @@ `b 0 0 1` // write bitmap with index 1 (tiny Linus cat) to row 0 column 0 `p 0 0` // prepare to "stream" a 16-Bit (5/6/5) RGB BMP image to row 0 column 0 `cat Bali160x128.bmp > /dev/ttyUSB0` // upload a BMP image +`s 0` // read BMP image from SD card starting at address 0 `a` // start paint application ## Enter emojis diff --git a/avrtft.c b/avrtft.c index 01be437..a8d6d79 100644 --- a/avrtft.c +++ b/avrtft.c @@ -32,6 +32,9 @@ #include "bmp.h" #include "touch.h" #include "paint.h" +#include "sdcard.h" + +bool sdcard = false; static volatile bool touch = false; @@ -54,6 +57,9 @@ // DDR_I2C |= (1 << PIN_SCL); // DDR_I2C |= (1 << PIN_SDA); + // set SD card reader CS as output pin + DDR_SDC |= (1 << PIN_SDCS); + // set display CS, D/C and RST pin as output pin DDR_DSPI |= (1 << PIN_DCS); DDR_DSPI |= (1 << PIN_DC); @@ -106,6 +112,7 @@ _delay_ms(1000); + sdcard = initSDCard(); initDisplay(); initTouchInt(); @@ -114,7 +121,11 @@ touch = false; // do something at the start - initPaint(); + if (sdcard) { + readSD(0); + } else { + initPaint(); + } // hackDemo(); while (true) { diff --git a/bmp.c b/bmp.c index bfb6d8b..cd81783 100644 --- a/bmp.c +++ b/bmp.c @@ -66,11 +66,11 @@ setStreamingData(true); } -void stream(uint8_t byte) { +uint8_t stream(uint8_t byte) { if (error) { // TODO recover from error condition // setStreaming(false); - return; + return BMP_ERROR; } push(byte); @@ -84,7 +84,7 @@ }; writeError(lines, 2); error = true; - return; + return BMP_ERROR; } } @@ -118,7 +118,7 @@ }; writeError(lines, 2); error = true; - return; + return BMP_ERROR; } } @@ -138,7 +138,7 @@ }; writeError(lines, 3); error = true; - return; + return BMP_ERROR; } } @@ -164,5 +164,25 @@ reset(); setStreamingData(false); // printString("write end\r\n"); + + return BMP_READY; } + + return BMP_BUSY; +} + +void readSD(uint32_t address) { + prepare(0, 0); + uint8_t block[SD_BLOCK_SIZE]; + uint8_t status; + do { + displayDes(); + bool success = readSingleBlock(address++, block); + displaySel(); + if (success) { + for (uint16_t j = 0; j < SD_BLOCK_SIZE && status == BMP_BUSY; j++) { + status = stream(block[j]); + } + } + } while (status == BMP_BUSY); } \ No newline at end of file diff --git a/bmp.h b/bmp.h index a1752de..a6ed3d8 100644 --- a/bmp.h +++ b/bmp.h @@ -12,6 +12,11 @@ #include #include #include "types.h" +#include "sdcard.h" + +#define BMP_READY 0 +#define BMP_BUSY 1 +#define BMP_ERROR 2 /** * Prepares to "stream" a BMP image via USART to the display, @@ -29,8 +34,16 @@ * are currently not supported. * * @param byte raw BMP byte recieved via USART + * @return status current status parsing BMP */ -void stream(uint8_t byte); +uint8_t stream(uint8_t byte); + +/** + * Reads a BMP image raw from the SD card starting at the given address. + * + * @param address start address + */ +void readSD(uint32_t address); #endif /* BMP_H */ diff --git a/cmd.c b/cmd.c index dcbfbee..05878fe 100644 --- a/cmd.c +++ b/cmd.c @@ -8,14 +8,8 @@ #include #include #include "cmd.h" -#include "usart.h" -#include "tft.h" -#include "display.h" -#include "font.h" -#include "hack.h" -#include "bitmaps.h" -#include "bmp.h" -#include "paint.h" + +extern bool sdcard; /** * Sets the frame buffer to the given 16-Bit (5/6/5) RGB color. @@ -67,6 +61,18 @@ prepare(row, col); } +static void bmpSD(char *data) { + strtok(data, " "); + char *end; + uint32_t address = strtol(strtok(NULL, " "), &end, 10); + + if (sdcard) { + readSD(address); + } else { + printString("SD card not inserted?\r\n"); + } +} + /** * Writes the Hack demo. */ @@ -82,6 +88,7 @@ case CMD_TEXT: text(data); break; case CMD_BITMAP: bitmap(data); break; case CMD_BMP: bmp(data); break; + case CMD_BMP_SD: bmpSD(data); break; case CMD_DEMO: demo(); break; case CMD_PAINT: initPaint(); break; default: break; diff --git a/cmd.h b/cmd.h index 5792f1c..7e6c926 100644 --- a/cmd.h +++ b/cmd.h @@ -8,6 +8,16 @@ #ifndef CMD_H #define CMD_H +#include "usart.h" +#include "tft.h" +#include "display.h" +#include "font.h" +#include "hack.h" +#include "bitmaps.h" +#include "bmp.h" +#include "sdcard.h" +#include "paint.h" + /** Clear frame buffer: 'c <0x0000 - 0xffff>'. */ #define CMD_CLEAR 'c' @@ -20,6 +30,9 @@ /** Prepare "streaming" a .bmp: 'p '. */ #define CMD_BMP 'p' +/** Read a .bmp: from SD card: 's
'. */ +#define CMD_BMP_SD 's' + /** Display Hack demo: 'd'. */ #define CMD_DEMO 'd' diff --git a/emojis.c b/emojis.c index 2fde0fb..c17b1c8 100644 --- a/emojis.c +++ b/emojis.c @@ -838,11 +838,11 @@ {'U', 16, THUMB_UP}, {'a', 16, SAD}, {'b', 16, BLUSH}, - {'e', 16, SMILE_TEAR}, + // {'e', 16, SMILE_TEAR}, sacrificed for flash memory {'g', 16, GRIN}, {'h', 16, HUG}, {'m', 16, MELT}, - {'n', 16, GRIN_TEAR}, + // {'n', 16, GRIN_TEAR}, sacrificed for flash memory {'o', 16, MOON}, {'r', 16, HERBS}, {'s', 16, SMILE}, diff --git a/emojis/emojis.sh b/emojis/emojis.sh index 2e627e7..94c841f 100755 --- a/emojis/emojis.sh +++ b/emojis/emojis.sh @@ -14,31 +14,31 @@ sleep 0.1 echo -e 't 16 32 \tg' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 16 48 \tn' > /dev/ttyUSB0 +# echo -e 't 16 48 \tn' > /dev/ttyUSB0 +# sleep 0.1 +echo -e 't 16 48 \tm' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 16 64 \tm' > /dev/ttyUSB0 +echo -e 't 16 64 \th' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 16 80 \th' > /dev/ttyUSB0 +echo -e 't 16 80 \tt' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 16 96 \tt' > /dev/ttyUSB0 +# echo -e 't 16 112 \te' > /dev/ttyUSB0 +# sleep 0.1 +echo -e 't 16 96 \ta' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 16 112 \te' > /dev/ttyUSB0 +echo -e 't 32 0 \tU' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 0 \ta' > /dev/ttyUSB0 +echo -e 't 32 16 \tD' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 16 \tU' > /dev/ttyUSB0 +echo -e 't 32 32 \tS' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 32 \tD' > /dev/ttyUSB0 +echo -e 't 32 48 \tA' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 48 \tS' > /dev/ttyUSB0 +echo -e 't 32 64 \tu' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 64 \tA' > /dev/ttyUSB0 +echo -e 't 32 80 \to' > /dev/ttyUSB0 sleep 0.1 -echo -e 't 32 80 \tu' > /dev/ttyUSB0 -sleep 0.1 -echo -e 't 32 96 \to' > /dev/ttyUSB0 -sleep 0.1 -echo -e 't 32 112 \tO' > /dev/ttyUSB0 +echo -e 't 32 96 \tO' > /dev/ttyUSB0 sleep 0.1 echo -e 't 48 0 \tH' > /dev/ttyUSB0 sleep 0.1 diff --git a/nbproject/Makefile-Custom.mk b/nbproject/Makefile-Custom.mk index 7b44f90..7103c2a 100644 --- a/nbproject/Makefile-Custom.mk +++ b/nbproject/Makefile-Custom.mk @@ -43,6 +43,7 @@ ${OBJECTDIR}/_ext/48b9b4a1/hack.o \ ${OBJECTDIR}/_ext/48b9b4a1/i2c.o \ ${OBJECTDIR}/_ext/48b9b4a1/paint.o \ + ${OBJECTDIR}/_ext/48b9b4a1/sdcard.o \ ${OBJECTDIR}/_ext/48b9b4a1/spi.o \ ${OBJECTDIR}/_ext/48b9b4a1/tft.o \ ${OBJECTDIR}/_ext/48b9b4a1/touch.o @@ -104,6 +105,10 @@ ${MKDIR} -p ${OBJECTDIR}/_ext/48b9b4a1 $(COMPILE.c) -g -DBAUD=9600 -DF_CPU=8000000UL -D__AVR_ATmega328P__ -D__flash=volatile -I. -o ${OBJECTDIR}/_ext/48b9b4a1/paint.o /home/dode/dev/avrtft/paint.c +${OBJECTDIR}/_ext/48b9b4a1/sdcard.o: /home/dode/dev/avrtft/sdcard.c + ${MKDIR} -p ${OBJECTDIR}/_ext/48b9b4a1 + $(COMPILE.c) -g -DBAUD=9600 -DF_CPU=8000000UL -D__AVR_ATmega328P__ -D__flash=volatile -I. -o ${OBJECTDIR}/_ext/48b9b4a1/sdcard.o /home/dode/dev/avrtft/sdcard.c + ${OBJECTDIR}/_ext/48b9b4a1/spi.o: /home/dode/dev/avrtft/spi.c ${MKDIR} -p ${OBJECTDIR}/_ext/48b9b4a1 $(COMPILE.c) -g -DBAUD=9600 -DF_CPU=8000000UL -D__AVR_ATmega328P__ -D__flash=volatile -I. -std=c99 -o ${OBJECTDIR}/_ext/48b9b4a1/spi.o /home/dode/dev/avrtft/spi.c diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 6247786..3072644 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -16,6 +16,8 @@ i2c.h paint.c paint.h + sdcard.c + sdcard.h spi.c tft.c touch.c @@ -81,6 +83,10 @@ + + + + diff --git a/pins.h b/pins.h index 2b47dda..1455c1b 100644 --- a/pins.h +++ b/pins.h @@ -25,6 +25,11 @@ #define PIN_SDA PC4 #define PIN_TINT PD2 // touch controller interrupt +/* SD Card Reader SPI */ +#define DDR_SDC DDRC +#define PORT_SDC PORTC +#define PIN_SDCS PC3 + /* Display SPI */ #define DDR_DSPI DDRB #define PORT_DSPI PORTB diff --git a/sdcard.c b/sdcard.c new file mode 100644 index 0000000..fd74de3 --- /dev/null +++ b/sdcard.c @@ -0,0 +1,314 @@ +/* + * File: sdcard.c + * Author: torsten.roemer@luniks.net + * + * Thanks to http://rjhcoding.com/avrc-sd-interface-1.php ff. + * + * Created on 24. Februar 2024, 00:13 + */ + +#include "sdcard.h" +#include "bmp.h" + +/** + * Transmits the given command, argument and CRC value. + * + * @param command + * @param argument + * @param crc + */ +static void command(uint8_t command, uint32_t argument, uint8_t crc) { + // 6-bit command, start bit is always 0, transmitter bit is 1 (host command) + transmit(command | 0x40); + + transmit(argument >> 24); + transmit(argument >> 16); + transmit(argument >> 8); + transmit(argument); + + // 7-bit crc, end bit is always 1 + transmit(crc | 0x01); +} + +/** + * Reads an R1 response and returns it. + * + * @return R1 + */ +static uint8_t readR1(void) { + uint8_t i = 0; + uint8_t response; + + // poll up to 8 times for response + while ((response = transmit(0xff)) == 0xff) { + i++; + if (i > 8) { + break; + } + } + + return response; +} + +/** + * Reads an R3/R7 response into the given array of 5 bytes. + * + * @param R3/R7 + */ +static void readR3_7(uint8_t *response) { + // read R1 + response[0] = readR1(); + + // stop reading if R1 indicates an error + if (response[0] > 0x01) { + return; + } + + response[1] = transmit(0xff); + response[2] = transmit(0xff); + response[3] = transmit(0xff); + response[4] = transmit(0xff); +} + +/** + * SPI selects the sd card with extra clocks before and after. + */ +static void select(void) { + transmit(0xff); + sdCardSel(); + transmit(0xff); +} + +/** + * SPI deselects the sd card with extra clocks before and after. + */ +static void deselect(void) { + transmit(0xff); + sdCardDes(); + transmit(0xff); +} + +/** + * Runs the power on sequence. + */ +static void powerOn(void) { + deselect(); + + // supply ramp up time + _delay_ms(1); + + // supply at least 74 clocks + for (uint8_t i = 0; i < 10; i++) { + transmit(0xff); + } + + deselect(); +} + +/** + * Sends CMD0 to set idle SPI mode and returns the R1 response. + * + * @return R1 + */ +static uint8_t sendIdle(void) { + select(); + + command(CMD0, CMD0_ARG, CMD0_CRC); + uint8_t response = readR1(); + + deselect(); + + return response; +} + +/** + * Sends CMD8 to check version and voltage and reads the R7 response + * into the given array of 5 bytes. + * + * @param R7 + */ +static void sendIfCond(uint8_t *response) { + select(); + + command(CMD8, CMD8_ARG, CMD8_CRC); + readR3_7(response); + + deselect(); +} + +/** + * Sends CMD55 to tell that an app command is next and returns the R1 response. + * + * @return R1 + */ +static uint8_t sendApp(void) { + select(); + + command(CMD55, CMD55_ARG, CMD55_CRC); + uint8_t response = readR1(); + + deselect(); + + return response; +} + +/** + * Sends CMD58 to read the OCR register and reads the R3 response + * into the given array of 5 bytes. + * + * @param R3 + */ +static void sendOCR(uint8_t *response) { + select(); + + command(CMD58, CMD58_ARG, CMD58_CRC); + readR3_7(response); + + deselect(); +} + +/** + * Sends ACMD41 to start initialization and returns the R1 response. + * + * @return R1 + */ +static uint8_t sendOpCond(void) { + select(); + + command(ACMD41, ACMD41_ARG, ACMD41_CRC); + uint8_t response = readR1(); + + deselect(); + + return response; +} + +bool readSingleBlock(uint32_t address, uint8_t *block) { + select(); + + command(CMD17, address, CMD17_CRC); + uint8_t response = readR1(); + uint8_t token = 0xff; + bool success = false; + + if (response == SD_SUCCESS) { + // read command was successful, wait for start block token + for (uint16_t attempt = 0; attempt < SD_MAX_READ && token == 0xff; attempt++) { + token = transmit(0xff); + } + + if (token == SD_START_BLOCK) { + // start block token received, 512 data bytes follow + for (uint16_t i = 0; i < SD_BLOCK_SIZE; i++) { + block[i] = transmit(0xff); + } + + // 16-bit CRC (ignore for now) + transmit(0xff); + transmit(0xff); + + success = true; + } + } + + deselect(); + + return success; +} + +bool writeSingleBlock(uint32_t address, uint8_t *block) { + select(); + + command(CMD24, address, CMD24_CRC); + uint8_t response = readR1(); + uint8_t token = 0xff; + bool success = false; + + if (response == SD_SUCCESS) { + // write command was successful, send start block token + transmit(SD_START_BLOCK); + + // write 512 data bytes + for (uint16_t i = 0; i < SD_BLOCK_SIZE; i++) { + transmit(block[i]); + } + + // wait for data response + for (uint16_t attempt = 0; attempt < SD_MAX_WRITE && token == 0xff; attempt++) { + token = transmit(0xff); + } + + if ((token & 0x0f) == 0x05) { + // data was accepted, wait while busy programming data + for (uint16_t attempt = 0; attempt < SD_MAX_WRITE && token == 0x00; attempt++) { + token = transmit(0xff); + } + + success = true; + } + } + + deselect(); + + return success; +} + +bool initSDCard(void) { + uint8_t response[5]; + + // power on + powerOn(); + + // go to idle state + response[0] = sendIdle(); + if (response[0] > 0x01) { + printString("sd card error 0\r\n"); + return false; + } + + // send interface condition + sendIfCond(response); + if (response[0] & (1 << SD_CMD_ILLEGAL)) { + printString("sd card is V1.x or not sd card\r\n"); + return false; + } else if (response[0] > 0x01) { + printString("sd card error 8\r\n"); + return false; + } else if (response[4] != 0xaa) { + printString("sd card echo pattern mismatch\r\n"); + return false; + } + + // initialize to ready state + uint8_t attempts = 0; + do { + if (attempts > 100) { + printString("sd card did not become ready\r\n"); + return false; + } + + // send app command + response[0] = sendApp(); + if (response[0] < 0x02) { + // start initialization + response[0] = sendOpCond(); + } + + _delay_ms(10); + attempts++; + } while (response[0] != SD_SUCCESS); + + // send operation conditions register + sendOCR(response); + if (response[0] > 0x01) { + printString("sd card error 58\r\n"); + return false; + } else if (!(response[1] & 0x80)) { + printString("sd card not ready\r\n"); + return false; + } + + printString("sd card ready\r\n"); + + return true; +} diff --git a/sdcard.h b/sdcard.h new file mode 100644 index 0000000..c64d95b --- /dev/null +++ b/sdcard.h @@ -0,0 +1,79 @@ +/* + * File: sdcard.h + * Author: torsten.roemer@luniks.net + * + * Created on 24. Februar 2024, 00:13 + */ + +#ifndef SDCARD_H +#define SDCARD_H + +#include +#include +#include "pins.h" +#include "spi.h" +#include "usart.h" + +#define CMD0 0 +#define CMD0_ARG 0x00000000 +#define CMD0_CRC 0x94 + +#define CMD8 8 +#define CMD8_ARG 0x0000001aa +#define CMD8_CRC 0x86 + +#define CMD17 17 +#define CMD17_CRC 0x00 + +#define CMD24 24 +#define CMD24_CRC 0x00 + +#define CMD55 55 +#define CMD55_ARG 0x00000000 +#define CMD55_CRC 0x00 + +#define CMD58 58 +#define CMD58_ARG 0x00000000 +#define CMD58_CRC 0x00 + +#define ACMD41 41 +#define ACMD41_ARG 0x40000000 +#define ACMD41_CRC 0x00 + +#define SD_MAX_READ 50000 // SPI clock ticks in 100 ms +#define SD_MAX_WRITE 125000 // SPI clock ticks in 250 ms +#define SD_BLOCK_SIZE 512 + +#define SD_CMD_ILLEGAL 2 + +#define SD_SUCCESS 0x00 +#define SD_START_BLOCK 0xfe + +/** + * Initializes the SD Card and returns true on success, false otherwise. + * + * @return true on success, false otherwise + */ +bool initSDCard(void); + +/** + * Reads a single block of 512 bytes starting at the given address + * into the given buffer and returns true on success, false otherwise. + * + * @param address address in 512 byte units + * @param block 512 byte buffer + * @return success + */ +bool readSingleBlock(uint32_t address, uint8_t *block); + +/** + * Writes a single block of 512 bytes starting at the given address + * from the given buffer and returns true on success, false otherwise. + * + * @param address address in 512 byte units + * @param block 512 byte buffer + * @return success + */ +bool writeSingleBlock(uint32_t address, uint8_t *block); + +#endif /* SDCARD_H */ diff --git a/spi.c b/spi.c index 9a189f1..7a2b75e 100644 --- a/spi.c +++ b/spi.c @@ -10,6 +10,14 @@ #include "pins.h" #include "spi.h" +void sdCardSel(void) { + PORT_SDC &= ~(1 << PIN_SDCS); +} + +void sdCardDes(void) { + PORT_SDC |= (1 << PIN_SDCS); +} + void displaySel(void) { PORT_DSPI &= ~(1 << PIN_DCS); } diff --git a/spi.h b/spi.h index 7fd9aba..6633090 100644 --- a/spi.h +++ b/spi.h @@ -9,6 +9,16 @@ #define SPI_H /** + * Selects the SD card to talk to via SPI. + */ +void sdCardSel(void); + +/** + * Delects the SD card to talk to via SPI. + */ +void sdCardDes(void); + +/** * Selects the display to talk to via SPI. */ void displaySel(void); diff --git a/usart.c b/usart.c index 9ea421c..26f9aad 100644 --- a/usart.c +++ b/usart.c @@ -89,7 +89,7 @@ void printHex(uint8_t data) { char buf[7]; - snprintf(buf, sizeof (buf), "0x%x\r\n", data); + snprintf(buf, sizeof (buf), "0x%02x\r\n", data); printString(buf); }