Casting type void to uint8_t

Hi, I'm working on esp32-s3 devkitc1 with FreeRTOS. I want to create an instance of a function to be able to call it depending on the given argument:

#define RED 2
#define GREEN 44
#define YELLOW 1

typedef int taskProfiler;

taskProfiler REDLEDProfiler = 0;
taskProfiler GREENLEDProfiler = 0;
taskProfiler YELLOWLEDProfiler = 0;

const uint16_t *redLed = (uint16_t*)RED;
const uint16_t *greenLed = (uint16_t*)GREEN;
const uint16_t *yellowLed = (uint16_t*)YELLOW;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(2000);

  xTaskCreate(ledControllerTask, "RED LED Task", 8192, (void*)redLed, 1, NULL);
  xTaskCreate(ledControllerTask, "GREEN LED Task", 8192, (void*)greenLed, 1, NULL);
  xTaskCreate(ledControllerTask, "YELLOW LED Task", 8192, (void*)yellowLed, 1, NULL);

}

void ledControllerTask(void *pvParameters)
{
  pinMode(pvParameters, OUTPUT);
  while(1)
  {
    digitalWrite(pvParameters, digitalRead(pvParameters)^1);
    vTaskDelay(500);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

After that I get error:

exit status 1
invalid conversion from 'void*' to 'uint8_t' {aka 'unsigned char'} [-fpermissive]

How I can properly cast void to uint8_t in this situation?

what do you expect from this when RED GREEN and YELLOW are just constant that will be textually replaced by a value

use
int redLed=2

then when you create the function use
xTaskCreate(ledControllerTask, "RED LED Task", 8192, (void*) &redLed, 1, NULL);

use that for the other two createtasks. I think that should take care of the errors

There is probably no need to cast the pointer to a void*.

xTaskCreate(ledControllerTask, "RED LED Task", 8192, &redLed, 1, NULL);

Just need to ensure redLed has memory allocated

it's common for generic function argument to be defined as void* because they allow the largest size variable. presumably the function referenced by the generic function pointer knows how to handle the argment

looks like these are constants, not ptrs. presumably you defined them as ptrs because they need to be cast as (void*) arguments, but they don't need to be ptrs to be cast as void *

when i tried recasting the void* argument to int, i get a loss of precision error. but it works when casting it to long. once cast as long it can be cast to smaller types

#include <stdio.h>

enum { Red, Green, Blue };
const char color1 = Green;

void
func (
    void    *arg)
{
    long   col = (long) arg;
    char   c   = col;
    printf ("%s: %ld %d\n", __func__, col, c);
}

int
main ()
{
    func ((void*) color1);
    func ((void*) Blue);

    return 0;
}

If it’s a pointer - not a value

An int (default for your enum) is 2 bytes on a UNO and so is a pointer so that works and the actual two bytes were passed but if you wanted to pass a real value like a long then if would fail on architectures where pointers are only two bytes. ➜ bad idea and not commonly done for value.

It’s really used for pointers like you find in memcpy() or memcmp()

void * memcpy ( void * destination, const void * source, size_t num );

The closest I could find is "ESP32S3 Dev Module".

This version compiles without error or warning:

const byte REDPin = 2;
const byte GREENPin = 44;
const byte YELLOWPin = 1;

uint16_t redLedArg = REDPin;
uint16_t greenLedArg = GREENPin;
uint16_t yellowLedArg = YELLOWPin;

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(2000);

  xTaskCreate(ledControllerTask, "RED LED Task", 8192, &redLedArg, 1, NULL);
  xTaskCreate(ledControllerTask, "GREEN LED Task", 8192, &greenLedArg, 1, NULL);
  xTaskCreate(ledControllerTask, "YELLOW LED Task", 8192, &yellowLedArg, 1, NULL);

}

void ledControllerTask(void *pvParameters)
{
  uint16_t pin = *(uint16_t *)pvParameters;
  pinMode(pin, OUTPUT);
  while (1)
  {
    digitalWrite(pin, digitalRead(pin) ^ 1);
    vTaskDelay(500);
  }
}

void loop()
{
  // put your main code here, to run repeatedly:
}

here's a definition for a generic function ptr that may be given a variety of types of value -- void (*fPtr)(void*)

Unfortunately that doesn't work for me

That works exactly as I want to. Thanks for help. I also make other solution for my problem:

typedef int taskProfiler;

uint8_t RED = 2;
uint8_t GREEN = 44;
uint8_t YELLOW = 1;

taskProfiler REDLEDProfiler = 0;
taskProfiler GREENLEDProfiler = 0;
taskProfiler YELLOWLEDProfiler = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(2000);

  xTaskCreate(ledControllerTask, "RED LED Task", 8192, &RED, 1, NULL);
  xTaskCreate(ledControllerTask, "GREEN LED Task", 8192, &GREEN, 1, NULL);
  xTaskCreate(ledControllerTask, "YELLOW LED Task", 8192, &YELLOW, 1, NULL);

}

void ledControllerTask(void *pvParameters)
{
  uint8_t *pin = (uint8_t *)pvParameters;
  pinMode(*pin, OUTPUT);
  while(1)
  {
    digitalWrite(*pin, digitalRead(*pin)^1);
    vTaskDelay(500);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

What I meant is that you pass the data as a pointer to the data. The data itself does not always fit into the 2 or 4 bytes allocated for the pointer

i believe i understand what you're trying to say -- you can pass any type of value as long as you pass a ptr to it. And yes, the C++ compiler generated an error when i tried casting a void* to a char, but the C compiler only generated a warning.

what i've seen is defining a type of largest possible size to accommodate any type of argument, ptr or not. i've posted code showing how a char can be passed

void* is an idiom, how can you have a ptr to a void. it means a value with unspecified type (not just void). i know you're going to say "but it's still a ptr".

There's no guarantee that this works, even if the integer type is the same size as a pointer:

From reinterpret_cast conversion - cppreference.com,

  1. A pointer can be converted to any integral type large enough to hold all values of its type (e.g. to std::uintptr_t)

  2. A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed; the same pointer may have multiple integer representations) The null pointer constant NULL or integer zero is not guaranteed to yield the null pointer value of the target type; static_cast or implicit conversion should be used for this purpose.

Indeed - it is…. Really not a good option.
If you want to accept various types of data and want to deal with them as a byte array for example then use void* and pass the pointer.

Anything else is not common practice nor recommended

kinda like "Animal Farm" - there's a reason it's just a warning with the C compiler (must be applications out there where the practice is accepted

i had a supervisor question the use of pointers, suggesting it was a bad practice.

what if the value being passed is a constant?

kernighan mentioned how C doesn't have the guard rails that C++ has for much larger applications

I think the more likely explanation is that they at some point they added a diagnostic for it, and to avoid breaking old code bases, they made it a warning instead of an error.

When casting a pointer to an integer, there are only two main scenarios:

  1. The pointer is actually a pointer to something. Converting it to an integer is only meaningful if you either A. later convert it back to a pointer, or B. use it as a unique integer for printing the address, using it as an index in some associative data structure, etc.
    In both cases, converting the pointer to an integer type that's too small to represent all pointer values is a bug.
  2. The pointer doesn't actually point to anything, but it's the result of an earlier cast from an integer to a pointer (e.g. reinterpret_cast<void *>(42)). In this case, casting the pointer back to an integer is meaningless, because you're not guaranteed to get back the original integer.

Then you ─ very carefully ─ use a const_cast: casting a pointer-to-const to a pointer-to-nonconst is allowed, as long as you don't use the pointer-to-nonconst to actually write to a constant object.

In this case, you can probably use C++'s std::thread, which makes this a non-issue, because it passes any arguments to the function, in a type-safe way:

void ledControllerTask(uint8_t color);

redTask = std::thread {ledControllerTask, RED};

If you need a specific configuration (task name, stack size, pinning to a specific core, etc.), you can use the esp_pthread_set_cfg() function before creating your threads.

We use a C++ compiler here. C let you do lots of things but the number of bytes allocated for a pointer is known and won’t fit larger data types so even in C it’s not a good idea

i don't understand why when copying data, the assumption is to copy the larger into the smaller instead of copying what fits into the smaller (which appear to be what c++ does).

the following works in C++. the long is truncated the size of char

void
func (
    void    *arg)
{
    long   col = (long) arg;
    char   c   = col;
    printf ("%s: %ld %d\n", __func__, col, c);
}
func: 0x120a 10

i don't understand why the compiler reports a loss of precision when going from (void*) to char, but not when going from long to char.

Because converting a pointer to a small integer type is always a bug, see my previous two posts.

Because there's nothing wrong with the conversion from long to char on its own (if you ignore the fact that the long originated from a pointer cast).

I get error, but if you cast pointer to intptr_t it works fine