WIP
This commit is contained in:
@@ -120,6 +120,8 @@ typedef struct Joystick {
|
|||||||
int errno;
|
int errno;
|
||||||
Button calibration_button;
|
Button calibration_button;
|
||||||
Joystick js;
|
Joystick js;
|
||||||
|
|
||||||
|
alignas(Freenove_ESP32_WS2812) uint8_t strip_storage[sizeof(Freenove_ESP32_WS2812)];
|
||||||
Freenove_ESP32_WS2812 *strip;
|
Freenove_ESP32_WS2812 *strip;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@@ -131,7 +133,7 @@ void initSerial()
|
|||||||
|
|
||||||
int init2812(Freenove_ESP32_WS2812 **strip)
|
int init2812(Freenove_ESP32_WS2812 **strip)
|
||||||
{
|
{
|
||||||
Freenove_ESP32_WS2812 *obj = new Freenove_ESP32_WS2812(8, PIN_2812, 0, TYPE_GRB);
|
Freenove_ESP32_WS2812 *obj = new(strip_storage) Freenove_ESP32_WS2812(8, PIN_2812, 0, TYPE_GRB);
|
||||||
if ( obj == NULL ) {
|
if ( obj == NULL ) {
|
||||||
ERROR(ERRNO_NULLPOINTER);
|
ERROR(ERRNO_NULLPOINTER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,22 +8,41 @@
|
|||||||
* How to select pin assignments on the ESP32 without relying on the tutorial
|
* How to select pin assignments on the ESP32 without relying on the tutorial
|
||||||
* How to calibrate joysticks
|
* How to calibrate joysticks
|
||||||
|
|
||||||
## Not trusting C++
|
## I still don't trust C++
|
||||||
|
|
||||||
To be fair, I give C++ more hate than it deserves. But the way some people write it really does the language no favors.
|
To be fair, I give C++ more hate than it deserves. But the way some people write it really does the language no favors.
|
||||||
|
|
||||||
The WS2812 tutorial(s) use a global variable with an initializer.
|
The WS2812 tutorial(s) use a global variable with an initializer that is called outside of the scope of any function. This is something that you can do in C++ that you really can't do in C, and I tend to think C is right where C++ is wrong.
|
||||||
|
|
||||||
I chose to use a heap allocation rather than use a global scope class initializer. Calling code outside of a function in a C-derived language just feels wrong to me. The objects live in the data segment, whcih is nice, but the compiler will generate some magic unspecified initializer function wherein it will call the constructors on the object, before it calls `main()`. But the order of those initializers is not guaranteed, so if A depends on B, you may be shit out of luck. And I especially don't trust global initializers that seem to be touching hardware. The whole thing just feels *wrong*.
|
```arduino
|
||||||
|
Freenove_ESP32_WS2812 strip = Freenove_ESP32_WS2812(8, PIN_2812, 0, TYPE_GRB);
|
||||||
|
```
|
||||||
|
|
||||||
|
In C the code wouldn't compile, because you can't execute code (beyond setting initializer values with builtin types) outside of a function. In C++, this is valid for constructors. The compiler puts the objects in the data segment, which is nice, as the alternative might be to call `new()` on it and get storage from the heap, which is nasty. But the compiler will generate some magic unspecified initializer function wherein it will call the constructors on the object, before it calls `main()`. And the order of those initializers is not guaranteed, so if A depends on B, you may be shit out of luck. And I especially don't trust global initializers that seem to be touching hardware. The whole thing just feels *wrong*.
|
||||||
|
|
||||||
<center><img alt="it's wrong" width="320" src="cartmanitswrong.gif"/></center>
|
<center><img alt="it's wrong" width="320" src="cartmanitswrong.gif"/></center>
|
||||||
|
|
||||||
Still, I could have done better. I was lazy and used `new` to create a new object on the heap. This is fine for the simple demo but for a real product I would have statically allocated the memory in the data segment and then pointed the `new()` operator at it somewhere in `setup()`.
|
I chose instead to force the initializer for the global variable to happen during the `init2812` routine, which is called during `setup`.
|
||||||
|
|
||||||
```arduino
|
```arduino
|
||||||
|
alignas(Freenove_ESP32_WS2812) uint8_t strip_storage[sizeof(Freenove_ESP32_WS2812)];
|
||||||
|
Freenove_ESP32_WS2812 *strip;
|
||||||
|
|
||||||
|
int init2812(Freenove_ESP32_WS2812 **strip)
|
||||||
|
{
|
||||||
|
Freenove_ESP32_WS2812 *obj = new(strip_storage) Freenove_ESP32_WS2812(8, PIN_2812, 0, TYPE_GRB);
|
||||||
|
if ( obj == NULL ) {
|
||||||
|
ERROR(ERRNO_NULLPOINTER);
|
||||||
|
}
|
||||||
|
obj->setBrightness(10);
|
||||||
|
obj->begin();
|
||||||
|
*strip = obj;
|
||||||
|
return ERRNO_SUCCESS;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
With this pattern, I get the global variable, that lives in the data segment, that is initialized exactly when I want it to be initialized. The downside (some might say) is that I'm stuck with using a pointer to reference the object in memory. Someone who fears pointers could use `std::optional` and `emplace()` to do the same thing, assuming that their toolchain supports it. But I feel like that's adding complexity in layers of abstraction. It doesn't get much simpler than saying "This object lives in memory *right over there* and here is a pointer to it".
|
||||||
|
|
||||||
## Selecting pin assignments: read the datasheets
|
## Selecting pin assignments: read the datasheets
|
||||||
|
|
||||||
Up until now I've been using whatever pins the tutorial chose because, well, I had enough to figure out while getting my fundamental electronics lessons. But ...
|
Up until now I've been using whatever pins the tutorial chose because, well, I had enough to figure out while getting my fundamental electronics lessons. But ...
|
||||||
|
|||||||
Reference in New Issue
Block a user