Felipe Moura 62894b9baf examples/spislave_test: Add user data receive
Improve example, now it can receive commands / data from user;
Fixed indentation issues.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
2025-02-17 23:06:58 +01:00

582 lines
15 KiB
C

/****************************************************************************
* apps/examples/spislv_test/spislv_test.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/select.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Define buffer sizes */
#define RX_BUFFER_SIZE 64
#define TX_BUFFER_SIZE 64
#define SOURCE_FILE "dev/spislv2" /* SPI device path */
/* Enumeration for operation modes */
typedef enum
{
MODE_WRITE,
MODE_LISTEN,
MODE_ECHO,
MODE_INVALID
} operation_mode_t;
/****************************************************************************
* Structure to hold program configurations
****************************************************************************/
typedef struct
{
operation_mode_t mode;
int num_bytes; /* Applicable for write mode */
int timeout_seconds; /* Applicable for all modes */
unsigned char tx_buffer[TX_BUFFER_SIZE];
} program_config_t;
/****************************************************************************
* Private Functions
****************************************************************************/
/**
* @brief Converts a single hexadecimal character to its byte value.
*
* @param c The hexadecimal character.
* @return The byte value of the hexadecimal character, or -1 if invalid.
*/
static int hexchar_to_byte(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
c = tolower(c);
if (c >= 'a' && c <= 'f')
{
return c - 'a' + 10;
}
return -1;
}
/**
* @brief Converts a hexadecimal string to a byte array.
*
* @param hexstr The input hexadecimal string.
* @param bytes The output byte array.
* @param max_bytes The maximum number of bytes to convert.
* @return The number of bytes converted, or -1 on error.
*/
static int hexstr_to_bytes(const char *hexstr, unsigned char *bytes,
size_t max_bytes)
{
int len;
int i;
len = strlen(hexstr);
if (len % 2 != 0 || len / 2 > max_bytes)
{
return -1;
}
for (i = 0; i < len / 2; i++)
{
int high = hexchar_to_byte(hexstr[2 * i]);
int low = hexchar_to_byte(hexstr[2 * i + 1]);
if (high == -1 || low == -1)
{
return -1;
}
bytes[i] = (high << 4) | low;
}
return len / 2;
}
/**
* @brief Parses and validates command-line arguments.
*
* @param argc Argument count.
* @param argv Argument vector.
* @param config Pointer to the program configuration structure.
* @return 0 on success, -1 on failure.
*/
static int parse_arguments(int argc, char *argv[],
program_config_t *config)
{
int opt;
/* Set default configurations */
config->mode = MODE_INVALID;
config->num_bytes = 0;
config->timeout_seconds = 10; /* Default timeout */
/* Parse command-line options */
while ((opt = getopt(argc, argv, "x:t:le")) != -1)
{
switch (opt)
{
case 'x':
if (config->mode != MODE_INVALID)
{
fprintf(stderr,
"Error: Multiple operation modes specified.\n");
return -1;
}
config->mode = MODE_WRITE;
config->num_bytes = atoi(optarg);
if (config->num_bytes <= 0 ||
config->num_bytes > TX_BUFFER_SIZE)
{
fprintf(stderr,
"Error: Invalid number of bytes for write mode.\n");
return -1;
}
break;
case 't':
config->timeout_seconds = atoi(optarg);
if (config->timeout_seconds <= 0)
{
fprintf(stderr,
"Error: Timeout must be a positive integer.\n");
return -1;
}
break;
case 'l':
if (config->mode != MODE_INVALID)
{
fprintf(stderr,
"Error: Multiple operation modes specified.\n");
return -1;
}
config->mode = MODE_LISTEN;
break;
case 'e':
if (config->mode != MODE_INVALID)
{
fprintf(stderr,
"Error: Multiple operation modes specified.\n");
return -1;
}
config->mode = MODE_ECHO;
break;
default:
fprintf(stderr, "Usage:\n");
fprintf(stderr,
" %s -x <num_bytes> [-t <timeout_seconds>] <hex_bytes>\n",
argv[0]);
fprintf(stderr,
" %s -l [-t <timeout_seconds>]\n", argv[0]);
fprintf(stderr,
" %s -e [-t <timeout_seconds>]\n", argv[0]);
printf("Examples:\n");
printf(" spislv -x 2 abba\n");
printf(" spislv -l -t 5\n");
printf(" spislv -e -t 10\n\n");
return -1;
}
}
/* Validate mutual exclusivity and required arguments */
if (config->mode == MODE_WRITE)
{
if (optind >= argc)
{
fprintf(stderr,
"Error: Missing hexadecimal bytes to send.\n");
fprintf(stderr,
"Usage: %s -x <num_bytes> [-t <timeout_seconds>] <hex_bytes>\n",
argv[0]);
return -1;
}
char *hex_input = argv[optind];
/* Verify the hexadecimal string length */
if (strlen(hex_input) != (size_t)(config->num_bytes * 2))
{
fprintf(stderr,
"Error: Hex string length must be %d characters\n"
"for %d bytes.\n",
config->num_bytes * 2, config->num_bytes);
return -1;
}
/* Convert hexadecimal string to byte array */
int converted = hexstr_to_bytes(hex_input, config->tx_buffer,
TX_BUFFER_SIZE);
if (converted != config->num_bytes)
{
fprintf(stderr, "Error: Invalid hexadecimal string.\n");
return -1;
}
}
else if (config->mode == MODE_INVALID)
{
fprintf(stderr, "Error: No operation mode specified.\n");
fprintf(stderr, "Usage:\n");
fprintf(stderr,
" %s -x <num_bytes> [-t <timeout_seconds>] <hex_bytes>\n",
argv[0]);
fprintf(stderr,
" %s -l [-t <timeout_seconds>]\n", argv[0]);
fprintf(stderr,
" %s -e [-t <timeout_seconds>]\n", argv[0]);
printf("Examples:\n");
printf(" spislv -x 2 abba\n");
printf(" spislv -l -t 5\n");
printf(" spislv -e -t 10\n");
return -1;
}
return 0;
}
/**
* @brief Executes the write mode: sends specified bytes to the master.
*
* @param config Pointer to the program configuration structure.
* @param fd File descriptor for the SPI device.
* @return 0 on success, -1 on failure.
*/
static int write_mode(program_config_t *config, int fd)
{
ssize_t bytes_written;
char data_str[3 * TX_BUFFER_SIZE + 1]; /* Buffer for debug string */
char *ptr = data_str;
int len;
int i;
for (i = 0; i < config->num_bytes; i++)
{
len = snprintf(ptr, sizeof(data_str) - (ptr - data_str),
"%02X ", config->tx_buffer[i]);
if (len < 0 || len >= (int)(sizeof(data_str) - (ptr - data_str)))
{
break;
}
ptr += len;
}
*ptr = '\0';
printf("Slave: Queuing %d bytes for sending to master: %s\n",
config->num_bytes, data_str);
bytes_written = write(fd, config->tx_buffer, config->num_bytes);
if (bytes_written < 0)
{
fprintf(stderr, "Error: Failed to write to %s: %s\n",
SOURCE_FILE, strerror(errno));
return -1;
}
else if (bytes_written != config->num_bytes)
{
fprintf(stderr, "Error: Incomplete write. Expected %d, got %zd\n",
config->num_bytes, bytes_written);
return -1;
}
return 0;
}
/**
* @brief Executes the listen-only mode: waits for data from the master.
*
* @param config Pointer to the program configuration structure.
* @param fd File descriptor for the SPI device.
* @return 0 on success, -1 on failure.
*/
static int listen_mode(program_config_t *config, int fd)
{
printf("Slave: Listen-only mode activated. Waiting for data\n"
" from master.\n");
return 0;
}
/**
* @brief Executes the echo mode: continuously echoes received data to
* the master.
*
* @param config Pointer to the program configuration structure.
* @param fd File descriptor for the SPI device.
* @return 0 on success, -1 on failure.
*/
static int echo_mode_func(program_config_t *config, int fd)
{
printf("Slave: Echo mode activated. Will echo received data until\n"
" timeout.\n");
return 0;
}
/**
* @brief Reads data from the SPI device with a specified timeout.
* Depending on the mode, it either exits after the first read or
* continues (echo mode).
*
* @param config Pointer to the program configuration structure.
* @param fd File descriptor for the SPI device.
* @return 0 on success, -1 on failure or timeout.
*/
static int read_with_timeout(program_config_t *config, int fd)
{
unsigned char buffer_rx[RX_BUFFER_SIZE];
ssize_t bytes_read;
ssize_t bytes_written;
int select_ret;
while (1)
{
fd_set read_fds;
struct timeval timeout;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
timeout.tv_sec = config->timeout_seconds;
timeout.tv_usec = 0;
select_ret = select(fd + 1, &read_fds,
NULL, NULL, &timeout);
if (select_ret == -1)
{
fprintf(stderr, "Error: Select failed: %s\n",
strerror(errno));
return -1;
}
else if (select_ret == 0)
{
if (config->mode == MODE_ECHO)
{
printf("Communication timeout after %d seconds. No more\n"
"data received from master.\n",
config->timeout_seconds);
}
else
{
printf("Communication timeout after %d seconds. No data\n"
"received from master.\n",
config->timeout_seconds);
}
return -1;
}
else
{
bytes_read = read(fd, buffer_rx, RX_BUFFER_SIZE);
if (bytes_read < 0)
{
fprintf(stderr, "Error: Failed to read from %s: %s\n",
SOURCE_FILE, strerror(errno));
return -1;
}
else if (bytes_read == 0)
{
printf("No data received from master.\n");
}
else
{
printf("Data received from master (%zd bytes): ",
bytes_read);
for (int i = 0; i < bytes_read; i++)
{
printf("%02X ", buffer_rx[i]);
}
printf("\n");
if (config->mode == MODE_ECHO)
{
bytes_written = write(fd, buffer_rx,
bytes_read);
if (bytes_written < 0)
{
fprintf(stderr,
"Error: Failed to write to %s: %s\n",
SOURCE_FILE, strerror(errno));
return -1;
}
else if (bytes_written != bytes_read)
{
printf("Error: Incomplete write during echo. ");
fprintf(stderr, "Expected %zd, got %zd\n",
bytes_read, bytes_written);
return -1;
}
printf("Echoed back %zd bytes to master.\n",
bytes_written);
}
if (config->mode != MODE_ECHO)
{
break;
}
}
}
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/**
* @brief Main function orchestrating the SPI slave operations based on
* user input.
*
* @param argc Argument count.
* @param argv Argument vector.
* @return 0 on success, -1 on failure.
*/
int main(int argc, char *argv[])
{
program_config_t config;
int fd;
int ret = 0;
if (parse_arguments(argc, argv, &config) != 0)
{
return -1;
}
fd = open(SOURCE_FILE, O_RDWR);
if (fd < 0)
{
fprintf(stderr, "Error: Failed to open %s: %s\n",
SOURCE_FILE, strerror(errno));
return -1;
}
/* Ensure the file descriptor is in blocking mode */
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
{
fprintf(stderr, "Error: Failed to get file flags: %s\n",
strerror(errno));
close(fd);
return -1;
}
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
{
fprintf(stderr, "Error: Failed to set blocking mode: %s\n",
strerror(errno));
close(fd);
return -1;
}
/* Execute the selected mode */
switch (config.mode)
{
case MODE_WRITE:
ret = write_mode(&config, fd);
if (ret != 0)
{
close(fd);
return -1;
}
break;
case MODE_LISTEN:
ret = listen_mode(&config, fd);
if (ret != 0)
{
close(fd);
return -1;
}
break;
case MODE_ECHO:
ret = echo_mode_func(&config, fd);
if (ret != 0)
{
close(fd);
return -1;
}
break;
default:
fprintf(stderr, "Error: Invalid operation mode.\n");
close(fd);
return -1;
}
ret = read_with_timeout(&config, fd);
if (ret != 0)
{
close(fd);
return -1;
}
printf("\n");
close(fd);
return 0;
}