This commit is contained in:
Obe Van Lierde
2023-11-21 16:17:27 +01:00
25 changed files with 10544 additions and 2688 deletions

1
.gitignore vendored
View File

@@ -59,3 +59,4 @@ project/Scripts
Scripts/
project/project\ Debug.launch
build/

22
CMakeLists.txt Normal file
View File

@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(WSAA_tests LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR})
enable_testing()
add_subdirectory(tests)

View File

@@ -6,6 +6,7 @@
- [Style Guide](#style-guide)
- [Editor Configuration](#editor-configuration)
- [Commit Messages Conventions](#commit-messages-conventions)
- [Writing File System Data to QSPI Flash](#writing-file-system-data-to-qspi-flash)
- [Documentation](#documentation)
## Used Libs, Compiler and Apps
@@ -52,6 +53,10 @@ Implement access right management
The body of a commit message may be used to explain the what and why of a commit.
## Writing File System Data to QSPI Flash
Please read the [llfs.md](./docs/llfs.md#enabling-the-external-loader-in-stm32cubeide) document for instructions
on how to enable the external loader in STM32CubeIDE.
## Documentation
Documentation is placed in the [docs](docs) folder.
If your part needs documentation (e.g. how to use tcp cmd interface), add a markdown file in the above-mentioned folder.
@@ -62,3 +67,4 @@ This folder contains the following documents:
- [logger.md](docs/logger.md): Logging and Debugging Messages
- [mkllfs.md](docs/mkllfs.md): Make Linked List File System
- [style_guide.md](docs/style_guide.md): Style Guide
- [tftp.md](docs/tftp.md): Trivial File Transfer Protocol

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -104,7 +104,7 @@ void main(void) {
#### Drawing text on the screen
```c
void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font);
void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT *font);
```
```c
@@ -116,7 +116,7 @@ void main(void) {
...
lcd_init(true);
...
lcd_display_text("This is a text string.", 10, 10, LCD_GREEN, LCD_BLACK, LCD_FONT16);
lcd_display_text("This is a text string.", 10, 10, LCD_GREEN, LCD_FONT16);
}
```
Display text on the LCD screen in a certain color. When text width exceeds BSP_LCD_GetXSize(), a text wrap will be performed. If the text wrap is between two will be injected.
@@ -194,13 +194,13 @@ void main(void) {
...
lcd_init(true);
...
// From a C-array
lcd_gif_t* gif = lcd_draw_gif(gif_array, gif_size, 0, 0);
// From the filesystem
lcd_gif_t* gif = lcd_draw_gif_from_fs("st.gif", 0, 0);
if (gif == NULL) {
LOG_WARNING("GIF could not be drawn");
}
@@ -229,15 +229,22 @@ Call this function before drawing over the GIF.
This function should not be called on a GIF that has already been stopped (GIFs with a loop count will stop automatically).
It is possible that the handler has been assigned to a new GIF, so it would stop the new GIF instead.
#### Stopping all GIF animations
```c
void lcd_stop_all_gifs(void);
```
This function stops all the GIF animations and frees the memory allocated for the GIF.
Call this function before drawing over the GIF.
#### Checking if a GIF is still running
```c
bool lcd_gif_is_playing(lcd_gif_t* gif);
```
NOTE: It is possible that the GIF has stopped playing, but another GIF has taken its slot and is still playing.
#### Clearing the LCD screen
#### Clearing the text on the LCD screen
```c
void lcd_clear(uint32_t color);
void lcd_clear_text(void);
```
```c
@@ -246,9 +253,29 @@ void lcd_clear(uint32_t color);
void main(void) {
...
lcd_init(true);
lcd_display_text("Hello world!", 0, 0, LCD_GREEN, LCD_FONT20);
...
lcd_clear(LCD_BLACK);
lcd_clear_text();
}
```
Clears the LCD screen to the specified color.
Clears all text strings on the LCD screen.
#### Clearing the images on the LCD screen
```c
void lcd_clear_images(void);
```
```c
#include "lcd_api.h"
void main(void) {
...
lcd_init(true);
lcd_draw_img_from_fs("st.bmp", 300, 100);
...
lcd_clear_images();
}
```
Clears all text strings on the LCD screen.

View File

@@ -24,6 +24,7 @@ restricting operations solely to read functions.
- [Reading a file](#reading-a-file)
- [Getting the number of files](#getting-the-number-of-files)
- [Using the POSIX file functions](#using-the-posix-file-functions)
- [Enabling the external loader in STM32CubeIDE](#enabling-the-external-loader-in-stm32cubeide)
## Initialization
Before using the llfs API, or the file related POSIX (fopen, fgetc, ...) functions, the filesystem must be initialized by calling `llfs_init()`.
@@ -161,3 +162,16 @@ The following functions are tested and working, but other functions might also w
- `rewind`
- `fstat`
- `fileno`
## Enabling the external loader in STM32CubeIDE
In order to write the file system data to the QSPI flash, the external loader must be enabled in STM32CubeIDE.
This can be done by opening the debug configuration:
![screenshot of step 1](img/ext_loader_step_1.png)
Then, in the `Debugger` tab:
3. Enable the `External Loader`
4. Click the `Scan` button
5. Select the correct loader: `N25Q128A_STM32F746G-DISCO, 0x90000000 ...`
![screenshot of step 2](img/ext_loader_step_2.png)

View File

@@ -7,7 +7,7 @@ The llfs filesystem is a flat filesystem, meaning that it does not support direc
The mkllfs utilit can be used to generate the `llfs_data.c` file. The `llfs_data.c` file from a directory with files.
A pre-compiled version can be download: [mkllfs.exe](https://github.com/Sani7/2023-Webservices_And_Applications/releases/tag/v0.2.0)
A pre-compiled version can be download: [mkllfs.exe](https://github.com/Sani7/2023-Webservices_And_Applications/releases/tag/v0.2.1)
## Usage
The mkllfs utility can be used as follows:

35
docs/tftp.md Normal file
View File

@@ -0,0 +1,35 @@
# TFTP
This is the documentation of the TFTP task
## Initialization
The TFTP task is initialized in the main function.
```c
// Initialize TFTP task
tftp_init();
```
## Deinitialization
If you would ever want to deinitialize the TFTP task, you can do so by calling the following function.
```c
// Deinitialize TFTP task
tftp_server_deinit();
```
## Usage
The TFTP task is used to receive and send files via TFTP.
### Receive a file
index.txt contains a list of files on the file system.
```bash
bash $ tftp <ip>
tftp $ get index.txt
```
### Send a file
You can only write to the following files:
- virtImage.bmp
```bash
bash $ tftp <ip>
tftp $ put <image.bmp> virtImage.bmp
```
- virtImage.gif
```bash
bash $ tftp <ip>
tftp $ put <image.gif> virtImage.gif
```

BIN
llfs-data/disappointed.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
llfs-data/dog.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
llfs-data/patpat-pat.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,7 +1,7 @@
/**
* @file main.c
* @brief Converts files to a C file that can be used by llfs (linked list file system).
* @version 0.2.0
* @version 0.2.1
* @author Lorenz C.
*/
@@ -10,7 +10,7 @@
#include <string.h>
#include "tinydir.h"
#define VERSION "0.2.0"
#define VERSION "0.2.1"
#define LLFS_VERSION "0.1.1"
#define MAX_PATH_LEN 256
@@ -125,7 +125,7 @@ int main(int argc, char** argv) {
// Make the last file the root file of the llfs
fprintf(out_file, "\n");
fprintf(out_file, "const struct llfs_data_file *llfs_root = &%s;\n", prev_llfs_name);
fprintf(out_file, "const struct llfs_data_file *llfs_root =%s%s;\n", (strcmp(prev_llfs_name, "NULL") == 0 ? " " : " &"), prev_llfs_name);
// Print the number of files
printf("Successfully converted %d files.\r\n", file_count);

View File

@@ -100,10 +100,9 @@ void lcd_task(void);
* @param[in] x_pos X-position
* @param[in] y_pos Y-position
* @param[in] color Color in which the text will be displayed, see preset colors in defines above
* @param[in] bg_color Background color for the text
* @param[in] font Font size, see defines above in file
*/
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font);
void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT *font);
/**
* @brief Draw BMP image on screen
@@ -144,13 +143,25 @@ void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos);
void lcd_draw_img_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos);
/**
* @brief Clear LCD screen
* Clears the whole LCD screen to the desired color
* @brief Clear LCD text
* Clears the text drawn on the LCD screen
*
*@param[in] color Color to which the LCD should be cleared
*/
void lcd_clear_text(void);
void lcd_clear(uint32_t color);
/**
* @brief Clear images
* Clears the images drawn on the LCD screen
*
*/
void lcd_clear_images(void);
/**
* @brief LCD stop all GIFs
* Stops all playing GIFs on lcd screen
*
*/
void lcd_stop_all_gifs(void);
/**
* @brief Draw GIF image on screen from memory

79
project/Core/Inc/tftp.h Normal file
View File

@@ -0,0 +1,79 @@
/**
* @file tftp.h
* @brief tftp server
* @author Sander S.
*/
#ifndef PROJECT_TFTP_H
#define PROJECT_TFTP_H
#include <tftp_server.h>
#ifndef TESTING
#include "lcd_api.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define LOGGER_LEVEL_ALL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "llfs.h"
#include "log.h"
#define TFTP_READ 0
#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif
typedef struct tftp_custom_file_s {
char* data;
size_t len;
char* name;
size_t offset;
} tftp_custom_file_t;
/**
* @brief Initialize the TFTP server
*/
void tftp_server_init(void);
/**
* @brief Uninitialize the TFTP server
*/
void tftp_server_deinit(void);
/**
* @brief Custom fseek function
*
* @param handle The custom file handle
* @param offset The offset
* @param whence The whence
*/
void tftp_custom_fseek(tftp_custom_file_t* handle, size_t offset, int whence);
/**
* @brief Custom fread function
*
* @param buf The buffer to read from
* @param bytes The amount of bytes to read
* @param handle The custom file handle
* @return The amount of bytes read
*/
size_t tftp_custom_fread(void* buf, size_t bytes, tftp_custom_file_t* handle);
/**
* @brief Custom fwrite function
*
* @param buf The buffer to write to
* @param bytes The amount of bytes to write
* @param handle The custom file handle
* @return The amount of bytes written
*/
size_t tftp_custom_fwrite(const void* buf, size_t bytes, tftp_custom_file_t* handle);
#ifdef __cplusplus
}
#endif
#endif // PROJECT_TFTP_H

View File

@@ -22,18 +22,22 @@ void lcd_init(bool bl_on) {
BSP_LCD_Init();
BSP_LCD_LayerDefaultInit(1, LCD_FB_START_ADDRESS);
BSP_LCD_LayerDefaultInit(0, LCD_FB_START_ADDRESS + (BSP_LCD_GetXSize() * BSP_LCD_GetYSize() * 4));
BSP_LCD_LayerDefaultInit(0, LCD_FB_START_ADDRESS + (BSP_LCD_GetXSize() * BSP_LCD_GetYSize()) * 4);
BSP_LCD_SelectLayer(0);
BSP_LCD_Clear(LCD_COLOR_BLACK);
BSP_LCD_Clear(0);
BSP_LCD_SelectLayer(1);
BSP_LCD_Clear(LCD_COLOR_BLACK);
BSP_LCD_Clear(0);
BSP_LCD_SetLayerVisible(0, ENABLE);
BSP_LCD_SetLayerVisible(1, ENABLE);
if (bl_on) {
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_12, GPIO_PIN_SET);
LOG_INFO(TAG, "LCD initialise with backlight");
return;
}
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_12, GPIO_PIN_RESET);
LOG_INFO(TAG, "LCD initialise without backlight");
}
void lcd_task(void) {
@@ -80,39 +84,42 @@ void lcd_task(void) {
}
}
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT* font) {
void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT* font) {
BSP_LCD_SelectLayer(1);
LOG_INFO(TAG, "Display text: %s @x=%d,y=%d", text, x_pos, y_pos);
uint16_t tot_length = x_pos + (strlen(text) * font->Width);
uint16_t tot_length = x_pos + ((uint16_t)strlen(text) * font->Width);
if ((x_pos % font->Width) != 0) {
x_pos -= (x_pos % font->Width);
}
BSP_LCD_SetTextColor(color);
BSP_LCD_SetBackColor(bg_color);
BSP_LCD_SetBackColor(0);
BSP_LCD_SetFont(font);
if (tot_length > BSP_LCD_GetXSize()) {
for (int i = 0; i < strlen(text); i++) {
for (unsigned int i = 0; i < (unsigned int)strlen(text); i++) {
if ((x_pos) > BSP_LCD_GetXSize() - (font->Width) * 2) {
if (isalpha(text[i - 1]) && isalpha(text[i])) {
BSP_LCD_DisplayChar(x_pos, y_pos, '-');
BSP_LCD_DisplayChar(x_pos, y_pos, (uint8_t)'-');
} else {
BSP_LCD_DisplayChar(x_pos, y_pos, text[i]);
BSP_LCD_DisplayChar(x_pos, y_pos, (uint8_t)text[i]);
}
x_pos = 0;
y_pos += font->Height;
continue;
}
BSP_LCD_DisplayChar(x_pos, y_pos, text[i]);
BSP_LCD_DisplayChar(x_pos, y_pos, (uint8_t)text[i]);
x_pos += font->Width;
}
return;
}
BSP_LCD_DisplayStringAt(x_pos, y_pos, text, LEFT_MODE);
BSP_LCD_DisplayStringAt(x_pos, y_pos, (uint8_t*)text, LEFT_MODE);
}
void lcd_draw_raw_img(const void* p_src, uint32_t x_pos, uint32_t y_pos, uint32_t x_size, uint32_t y_size, uint32_t color_mode) {
LOG_INFO(TAG, "Displaying raw image: @x=%d, @y=%d, width=%d, height=%d", x_pos, y_pos, x_size, y_size);
BSP_LCD_SelectLayer(0);
uint32_t address = hLtdcHandler.LayerCfg[1].FBStartAdress + (((BSP_LCD_GetXSize() * y_pos) + x_pos) * (4));
void* p_dst = (void*)address;
@@ -146,23 +153,49 @@ void lcd_draw_raw_img(const void* p_src, uint32_t x_pos, uint32_t y_pos, uint32_
}
void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos) {
LOG_INFO(TAG, "Displaying BMP image: @x=%d, @y=%d", x_pos, y_pos);
BSP_LCD_SelectLayer(0);
BSP_LCD_DrawBitmap(x_pos, y_pos, bmp_buff);
}
void lcd_draw_img_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos) {
LOG_INFO(TAG, "Displaying BMP image %s: @x=%d, @y=%d", name, x_pos, y_pos);
BSP_LCD_SelectLayer(0);
llfs_file_t* file = llfs_file_open(name);
if (file != NULL) {
BSP_LCD_DrawBitmap(x_pos, y_pos, (uint8_t*)file->data);
return;
}
LOG_WARN(TAG, "File \"%s\" not found", file->name);
LOG_WARN(TAG, "File \"%s\" not found", name);
}
void lcd_clear(uint32_t color) {
BSP_LCD_Clear(color);
void lcd_clear_text(void) {
LOG_INFO(TAG, "Clear text");
BSP_LCD_SelectLayer(1);
BSP_LCD_Clear(0);
}
void lcd_clear_images(void) {
LOG_INFO(TAG, "Clear images");
BSP_LCD_SelectLayer(0);
for (uint8_t i = 0; i < LCD_MAX_GIFS; i++) {
if (gifs[i].src != NULL) {
lcd_stop_gif(&gifs[i]);
}
}
BSP_LCD_Clear(0);
}
void lcd_stop_all_gifs(void) {
for (uint8_t i = 0; i < LCD_MAX_GIFS; i++) {
if (gifs[i].src != NULL) {
lcd_stop_gif(&gifs[i]);
}
}
}
lcd_gif_t* lcd_draw_gif(uint8_t* src, size_t size, uint32_t x_pos, uint32_t y_pos) {
BSP_LCD_SelectLayer(0);
lcd_gif_t* gif;
// Get a free GIF slot
@@ -174,6 +207,7 @@ lcd_gif_t* lcd_draw_gif(uint8_t* src, size_t size, uint32_t x_pos, uint32_t y_po
// Open the GIF and reset slot values
GIF_begin(&(gif->gif), GIF_PALETTE_RGB888);
if (GIF_openRAM(&(gif->gif), src, (int)size, gif_draw_cb)) {
LOG_INFO(TAG, "Draw GIF: @x=%d, @y=%d with size: %d", x_pos, y_pos, size);
gif->src = src;
gif->x_pos = x_pos;
gif->y_pos = y_pos;
@@ -193,6 +227,7 @@ lcd_gif_t* lcd_draw_gif(uint8_t* src, size_t size, uint32_t x_pos, uint32_t y_po
}
lcd_gif_t* lcd_draw_gif_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos) {
BSP_LCD_SelectLayer(0);
lcd_gif_t* gif;
llfs_file_t* file;
@@ -202,7 +237,7 @@ lcd_gif_t* lcd_draw_gif_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos
LOG_WARN(TAG, "File \"%s\" not found", name);
return NULL;
}
LOG_INFO(TAG, "Draw GIF %s", name);
// Draw the GIF using the file data
gif = lcd_draw_gif((uint8_t*)file->data, file->len, x_pos, y_pos);
return gif;
@@ -250,13 +285,14 @@ static inline void free_gif_slot(lcd_gif_t* gif) {
* @param pDraw Pointer to the GIFDRAW struct
*/
static void gif_draw_cb(GIFDRAW* pDraw) {
BSP_LCD_SelectLayer(0);
lcd_gif_t* gif = (lcd_gif_t*)pDraw->pUser;
uint8_t* palette = pDraw->pPalette24; // The RGB888 color palette
uint8_t* p_src = pDraw->pPixels; // Source pixel pointer
int y = pDraw->iY + pDraw->y; // Current line being drawn
// Calculate the destination address of the first pixel in the line
uint32_t address = hLtdcHandler.LayerCfg[1].FBStartAdress + (((BSP_LCD_GetXSize() * gif->y_pos) + gif->x_pos) * 4)
uint32_t address = hLtdcHandler.LayerCfg[0].FBStartAdress + (((BSP_LCD_GetXSize() * gif->y_pos) + gif->x_pos) * 4)
+ (y * (BSP_LCD_GetXSize() * 4));
// Restore the background
@@ -285,4 +321,4 @@ static void gif_draw_cb(GIFDRAW* pDraw) {
// Draw the pixel
((uint32_t *)address)[x] = color;
}
}
}

View File

@@ -10,8 +10,8 @@
#include <stdio.h>
#include <string.h>
#define LOGGER_LEVEL_WARN
#include "llfs.h"
#include "log.h"
#include "llfs.h"
/**
* @brief The maximum number of files that can be opened concurrently using the POSIX API
@@ -20,8 +20,8 @@
extern struct llfs_data_file* llfs_root;
const char* TAG = "llfs";
size_t file_count = 0;
FILE* file_table[POSIX_MAX_FILES];
static size_t file_count = 0; // Cache for the number of files in the filesystem
static FILE* file_table[POSIX_MAX_FILES];
static int new_file_table_entry(void);
static int free_file_table_entry(int file_id);
@@ -41,6 +41,14 @@ int8_t llfs_init(void) {
file_table[STDOUT_FILENO] = stdout;
file_table[STDERR_FILENO] = stderr;
// Calculate the number of files in the filesystem and cache it
const struct llfs_data_file* file = llfs_root;
file_count = 0;
while (file != NULL) {
file_count++;
file = file->next;
}
return 0;
}
@@ -57,7 +65,7 @@ size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter) {
}
// Iterate over all files in the filesystem
while (file != NULL && file_count < max_files) {
while (file != NULL && count < max_files) {
// Filter out files with a filename that does not match the filter
if (filter != NULL) {
if (!file_ext_cmp(file->name, filter)) {
@@ -130,13 +138,6 @@ llfs_file_t* llfs_next_file(void** mem, char* filter) {
}
size_t llfs_file_count(void) {
if (file_count == 0) {
const struct llfs_data_file* file = llfs_root;
while (file != NULL) {
file_count++;
file = file->next;
}
}
return file_count;
}
@@ -218,7 +219,7 @@ int _close(int file_id) {
* @param len
* @return
*/
int _read(int file_id, char* ptr, int len) {
size_t _read(int file_id, char* ptr, int len) {
FILE* stream;
llfs_file_t* llfs_file;
size_t bytes_read;
@@ -256,7 +257,7 @@ int _read(int file_id, char* ptr, int len) {
memcpy(ptr, llfs_file->data + stream->_offset, bytes_read);
stream->_offset += (off_t)bytes_read;
return (int)bytes_read;
return bytes_read;
}
/**
@@ -281,7 +282,7 @@ int isatty(int file) {
* @param dir
* @return
*/
int _lseek(int file, int ptr, int dir) {
off_t _lseek(int file, int ptr, int dir) {
FILE* stream;
if (file == STDIN_FILENO || file == STDOUT_FILENO || file == STDERR_FILENO) {
@@ -310,7 +311,7 @@ int _lseek(int file, int ptr, int dir) {
return -1;
}
return 0;
return stream->_offset;
}
/**
@@ -409,8 +410,6 @@ static FILE* file_id_to_stream(int file_id) {
return file_table[file_id];
}
/**
* @brief Check if a filename ends with a given extension
*

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@
#include "log.h"
#include "llfs.h"
#include "lcd_api.h"
#include "tftp.h"
/* USER CODE END Includes */
@@ -120,7 +121,7 @@ int main(void)
BSP_QSPI_MemoryMappedMode();
WRITE_REG(QUADSPI->LPTR, 0xFFF);
// Clear terminal
/* Clear terminal */
printf(CLEAR_SCREEN);
/* Initialize the LCD */
@@ -129,6 +130,8 @@ int main(void)
/* Initialize the filesystem */
llfs_init();
/* Initialize the tftp server */
tftp_server_init();
/* USER CODE END 2 */
/* Infinite loop */

323
project/Core/Src/tftp.c Normal file
View File

@@ -0,0 +1,323 @@
/**
* @file tftp.c
* @brief tftp server
* @author Sander S.
*/
#include "tftp.h"
#define VIRT_INDEX_TXT 0
#define VIRT_IMAGE_BMP 1
#define VIRT_IMAGE_GIF 2
#define VIRT_TEXT_TXT 3
#define MAX_VIRT_FILES 4
#define IMAGE_BUFFER_SIZE 81920
static const char* TAG = "tftp_server";
static tftp_custom_file_t virt_file[] = {{.name = "index.txt", .data = NULL, .len = 0, .offset = 0},
{.name = "virtImage.bmp", .data = NULL, .len = 0, .offset = 0},
{.name = "virtImage.gif", .data = NULL, .len = 0, .offset = 0},
{.name = "virtText.txt", .data = NULL, .len = 0, .offset = 0}};
static int str_cat_str(char* dest, size_t dest_size, const char* src) {
size_t dest_len = strlen(dest);
size_t src_len = strlen(src);
if (dest_len + src_len > dest_size) {
return -1;
}
memcpy(dest + dest_len, src, src_len);
return 0;
}
static int str_cat(char* dest, size_t dest_size, char c) {
size_t dest_len = strlen(dest);
if (dest_len + 1 > dest_size) {
return -1;
}
dest[dest_len] = c;
dest[dest_len + 1] = '\0';
return 0;
}
/**
* @brief tftp custom file functions to set the offset and read the data
* @param[in,out] handle Custom file handles
* @param[in] offset The offset to set
* @param[in] whence The origin of the offset
*/
void tftp_custom_fseek(tftp_custom_file_t* handle, size_t offset, int whence) {
switch (whence) {
case SEEK_SET:
handle->offset = offset;
break;
case SEEK_CUR:
handle->offset += offset;
break;
case SEEK_END:
break;
}
if (handle->offset > handle->len) {
handle->offset = handle->len;
}
}
/**
* @brief tftp custom file functions to read the data
* auto rolling over the offset
* if the bytes to read is bigger than the remaining bytes
* it will read the remaining bytes and set the bytes to 0
* @param[out] buf The buffer to write the data to
* @param[in] bytes The number of bytes to read
* @param[in,out] handle Custom file handles
*/
size_t tftp_custom_fread(void* buf, size_t bytes, tftp_custom_file_t* handle) {
if (handle->offset + bytes > handle->len) {
bytes = handle->len - handle->offset;
}
memcpy(buf, handle->data + handle->offset, bytes);
handle->offset += bytes;
((char*)buf)[bytes] = '\0';
if (handle->offset > handle->len) {
bytes = 0;
}
return bytes;
}
/**
* @brief tftp custom file functions to write the data
* auto rolling over the offset
*
* @param buf The buffer to write the data to
* @param bytes The number of bytes to write
* @param handle The handle to the file to write to
* @return The number of bytes written
*/
size_t tftp_custom_fwrite(const void* buf, size_t bytes, tftp_custom_file_t* handle) {
if (handle->offset + bytes > handle->len) {
bytes = handle->len - handle->offset;
}
memcpy(handle->data + handle->offset, buf, bytes);
handle->offset += bytes;
if (handle->offset > handle->len) {
bytes = 0;
}
return bytes;
}
/**
* @brief tftp helper functions
*/
/**
* @brief This function is called when a file is opened
* It should return a handle to the file or NULL if the file does not exist
* The handle contains a ptr to or the actual file data or a virtual file
*
* @param fname The name of the file to open
* @param mode Mode string from TFTP RFC
* @param write Flag indicating read (0) or write (!= 0) access
* @return void* File handle supplied to other functions
*/
void* tftp_open(const char* fname, const char* mode, uint8_t write) {
LOG_INFO(TAG, "Opening %s", fname);
UNUSED(mode);
if (strcmp(fname, virt_file[VIRT_INDEX_TXT].name) == 0 && write == TFTP_READ) {
tftp_custom_fseek(&virt_file[VIRT_INDEX_TXT], 0, SEEK_SET);
return &virt_file[0];
} else if (strcmp(fname, virt_file[VIRT_IMAGE_BMP].name) == 0 && write != TFTP_READ) {
return &virt_file[VIRT_IMAGE_BMP];
} else if (strcmp(fname, virt_file[VIRT_IMAGE_GIF].name) == 0 && write != TFTP_READ) {
return &virt_file[VIRT_IMAGE_GIF];
} else if (strcmp(fname, virt_file[VIRT_TEXT_TXT].name) == 0 && write != TFTP_READ) {
return &virt_file[VIRT_TEXT_TXT];
}
return fopen(fname, write ? "wb" : "rb");
}
/**
* @brief This function is called when a file is closed
*
* @param handle The handle to the file to close
*/
void tftp_close(void* handle) {
LOG_INFO(TAG, "closing file");
if (handle == NULL) {
LOG_CRIT(TAG, "handle is null");
return;
}
if (handle == &virt_file[VIRT_IMAGE_BMP]) {
lcd_clear_images();
lcd_clear_text();
lcd_draw_bmp_img((uint8_t*)virt_file[VIRT_IMAGE_BMP].data, 0, 0);
}
if (handle == &virt_file[VIRT_IMAGE_GIF]) {
lcd_clear_images();
lcd_clear_text();
lcd_draw_gif((uint8_t*)virt_file[VIRT_IMAGE_GIF].data, virt_file[VIRT_IMAGE_GIF].offset, 0, 0);
}
if (handle == &virt_file[VIRT_TEXT_TXT]) {
lcd_clear_images();
lcd_clear_text();
lcd_display_text((uint8_t*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_FONT16);
}
if (handle == &virt_file[VIRT_INDEX_TXT] || handle == &virt_file[VIRT_IMAGE_BMP]
|| handle == &virt_file[VIRT_IMAGE_GIF] || handle == &virt_file[VIRT_TEXT_TXT]) {
((tftp_custom_file_t*)handle)->offset = 0;
return;
}
fclose((FILE*)handle);
}
/**
* @brief This function is called when a file is read
* The virtual files are filtered out first
* then the file is trying to get read from the llfs
*
* @param handle File handle returned by open()
* @param buf Target buffer to copy read data to
* @param bytes Number of bytes to copy to buf
* @return int >= 0: Success; < 0: Error
*/
int tftp_read(void* handle, void* buf, int bytes) {
int ret = 0;
LOG_INFO(TAG, "reading file");
if (handle == NULL) {
LOG_CRIT(TAG, "handle is null");
return -1;
}
FILE* file = (FILE*)handle;
if ((tftp_custom_file_t*)file == &virt_file[VIRT_INDEX_TXT]) {
ret = (int)tftp_custom_fread(buf, (size_t)bytes, (tftp_custom_file_t*)file);
return ret;
} else if ((tftp_custom_file_t*)file == &virt_file[VIRT_IMAGE_BMP]) {
LOG_CRIT(TAG, "Exception: Trying to read a write only file");
return -1;
} else if ((tftp_custom_file_t*)file == &virt_file[VIRT_IMAGE_GIF]) {
LOG_CRIT(TAG, "Exception: Trying to read a write only file");
return -1;
} else if ((tftp_custom_file_t*)file == &virt_file[VIRT_TEXT_TXT]) {
LOG_CRIT(TAG, "Exception: Trying to read a write only file");
return -1;
}
ret = (int)fread(buf, sizeof(uint8_t), (size_t)bytes, file);
if (ret <= 0) {
return -1;
}
return ret;
}
/**
* @brief This function is called when a file is written
*
* @param handle File handle returned by open()
* @param p PBUF adjusted such that payload pointer points to the beginning of write data.
* In other words, TFTP headers are stripped off.
* @return int >= 0: Success; < 0: Error
*/
int tftp_write(void* handle, struct pbuf* p) {
LOG_INFO(TAG, "Writing file");
tftp_custom_file_t* file = (tftp_custom_file_t*)handle;
if (file == &virt_file[VIRT_IMAGE_BMP] || file == &virt_file[VIRT_IMAGE_GIF] || file == &virt_file[VIRT_TEXT_TXT]) {
return (int)tftp_custom_fwrite(p->payload, (size_t)(p->len), file);
}
return -1;
}
/**
* @brief This function creates the file list for index.txt
*/
void init_index(void) {
size_t len = 0;
// Add len of the virt files to the size
for (int i = 0; i < MAX_VIRT_FILES; i++) {
len += strlen(virt_file[i].name) + 1;
}
void* mem = NULL; // Pointer for internal use by the llfs library
llfs_file_t* file;
while ((file = llfs_next_file(&mem, NULL)) != NULL) {
len += strlen(file->name) + 1;
}
len++; // +1 for the \0
virt_file[VIRT_INDEX_TXT].data = calloc(len, sizeof(char));
if (virt_file[VIRT_INDEX_TXT].data == NULL) {
LOG_FATAL(TAG, "Could not allocate memory for index.txt");
return;
}
virt_file[VIRT_INDEX_TXT].len = len;
for (int i = 0; i < MAX_VIRT_FILES; i++) {
str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, virt_file[i].name);
str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n');
}
mem = NULL;
file = NULL;
while ((file = llfs_next_file(&mem, NULL)) != NULL) {
str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, file->name);
str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n');
}
}
struct tftp_context tftpContext_s = {.open = tftp_open, .close = tftp_close, .read = tftp_read, .write = tftp_write};
/**
* @brief Initialize tftp server
*/
void tftp_server_init(void) {
LOG_INFO(TAG, "Initializing tftp server");
// init the index.txt virt_file
init_index();
LOG_DEBUG(TAG, "index.txt: %s", virt_file[VIRT_INDEX_TXT].data);
// init the virtImage.raw virt_file with 80kb of ram
virt_file[VIRT_IMAGE_BMP].data = calloc(IMAGE_BUFFER_SIZE, sizeof(char));
if (virt_file[VIRT_IMAGE_BMP].data == NULL) {
LOG_FATAL(TAG, "Could not allocate memory for virtImage.bmp/virtImage.gif");
return;
}
virt_file[VIRT_IMAGE_BMP].len = IMAGE_BUFFER_SIZE;
virt_file[VIRT_IMAGE_GIF].data = virt_file[VIRT_IMAGE_BMP].data;
virt_file[VIRT_IMAGE_GIF].len = virt_file[VIRT_IMAGE_BMP].len;
virt_file[VIRT_TEXT_TXT].data = virt_file[VIRT_IMAGE_BMP].data;
virt_file[VIRT_TEXT_TXT].len = virt_file[VIRT_IMAGE_BMP].len;
// Init the tftp server
if (tftp_init(&tftpContext_s) != ERR_OK) {
LOG_FATAL(TAG, "Could not initialize tftp server");
return;
}
LOG_INFO(TAG, "tftp server initialized successfully");
}
void tftp_server_deinit(void) {
LOG_INFO(TAG, "Deinitializing tftp server");
tftp_cleanup();
LOG_INFO(TAG, "tftp server deinitialized successfully");
free(virt_file[VIRT_INDEX_TXT].data);
virt_file[VIRT_INDEX_TXT].data = NULL;
virt_file[VIRT_INDEX_TXT].len = 0;
virt_file[VIRT_INDEX_TXT].offset = 0;
free(virt_file[VIRT_IMAGE_BMP].data);
virt_file[VIRT_IMAGE_BMP].data = NULL;
virt_file[VIRT_IMAGE_BMP].len = 0;
virt_file[VIRT_IMAGE_GIF].data = NULL;
virt_file[VIRT_IMAGE_GIF].len = 0;
virt_file[VIRT_TEXT_TXT].data = NULL;
virt_file[VIRT_TEXT_TXT].len = 0;
}

40
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
find_package(GTest REQUIRED)
# Third Party
include_directories(${GTEST_INCLUDE_DIR})
link_directories(${GTEST_LIB_DIR})
# tests
file(GLOB_RECURSE TEST_SOURCES "*.cpp" "*.c")
add_executable(tests)
target_compile_definitions(tests
PRIVATE
"TESTING"
)
target_sources(tests
PRIVATE
${TEST_SOURCES}
../project/Core/Src/tftp.c
)
target_compile_options(tests PRIVATE $<$<CONFIG:Debug>:
-Wall -Wextra -pedantic-errors -Wconversion -Wsign-conversion
>)
target_link_libraries(tests
PRIVATE
gtest
GTest::gtest_main
)
target_include_directories(tests
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${PROJECT_BINARY_DIR}
../project/Core/Inc/
)
include(GoogleTest)
gtest_discover_tests(tests)

31
tests/mocs.c Normal file
View File

@@ -0,0 +1,31 @@
#include "tftp.h"
struct llfs_data_file llfs_root = {
.data = NULL,
.len = 0,
.name = "root",
.next = NULL,
};
void tftp_cleanup(void) {
}
uint32_t logger_get_timestamp(void) {
return 0;
}
int tftp_init(struct tftp_context* context) {
return 0;
}
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font) {
}
void lcd_clear(uint32_t color) {
}
void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos) {
}

76
tests/tfpt_tests.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include <gtest/gtest.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "tftp.h"
tftp_custom_file_t file = {
.data = (char*)"1234567890",
.len = 11,
.name = (char*)"test.txt",
.ofset = 0
};
tftp_custom_file_t write_file = {
.data = NULL,
.len = 0,
.name = (char*)"test.txt",
.ofset = 0
};
TEST(TFTP, custom_fseek)
{
tftp_custom_fseek(&file, 5, SEEK_SET);
EXPECT_EQ(file.ofset, 5);
tftp_custom_fseek(&file, 5, SEEK_CUR);
EXPECT_EQ(file.ofset, 10);
}
TEST(TFTP, custom_fread)
{
char buf[11];
tftp_custom_fseek(&file, 0, SEEK_SET);
size_t bytes = tftp_custom_fread(buf, 11, &file);
EXPECT_EQ(bytes, 11);
EXPECT_EQ(file.ofset, 11);
EXPECT_EQ(memcmp(buf, "1234567890", 10), 0);
memset(buf, 0, 11);
tftp_custom_fseek(&file, 0, SEEK_SET);
bytes = tftp_custom_fread(buf, 11, &file);
EXPECT_EQ(bytes, 11);
EXPECT_EQ(memcmp(buf, "1234567890", 10), 0);
memset(buf, 0, 11);
tftp_custom_fseek(&file, 0, SEEK_SET);
bytes = tftp_custom_fread(buf, 5, &file);
EXPECT_EQ(bytes, 5);
EXPECT_EQ(memcmp(buf, "12345", 5), 0);
memset(buf, 0, 11);
bytes = tftp_custom_fread(buf, 5, &file);
EXPECT_EQ(bytes, 5);
EXPECT_EQ(memcmp(buf, "67890", 5), 0);
}
TEST(TFTP, custom_fwrite) {
write_file.data = (char*)malloc(21 * sizeof(char));
write_file.len = 21;
tftp_custom_fwrite("0987654321", 10, &write_file);
EXPECT_EQ(write_file.ofset, 10);
EXPECT_EQ(write_file.len, 21);
EXPECT_EQ(memcmp(write_file.data, "0987654321", 10), 0);
tftp_custom_fwrite("1234567890", 10, &write_file);
EXPECT_EQ(write_file.ofset, 20);
EXPECT_EQ(write_file.len, 21);
EXPECT_EQ(memcmp(write_file.data, "09876543211234567890", 20), 0);
free(write_file.data);
write_file.data = NULL;
write_file.len = 0;
}

45
tests/tftp_server.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include <stdint.h>
struct pbuf {
struct pbuf *next;
void *payload;
uint16_t tot_len;
uint16_t len;
uint8_t type_internal;
uint8_t flags;
//LWIP_PBUF_REF_T ref;
uint8_t if_idx;
};
typedef void sFONT;
#define ERR_OK 0
#define LCD_COLOR_BLACK 0
#define LCD_COLOR_WHITE 1
#define LCD_FONT16 0
struct tftp_context {
void* (*open)(const char* fname, const char* mode, uint8_t write);
void (*close)(void* handle);
int (*read)(void* handle, void* buf, int bytes);
int (*write)(void* handle, struct pbuf* p);
};
void tftp_cleanup(void);
uint32_t logger_get_timestamp(void);
int tftp_init(struct tftp_context* context);
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font);
void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos);
void lcd_clear(uint32_t color);
#ifdef __cplusplus
}
#endif