add firmware
This commit is contained in:
parent
f165ccdc95
commit
580bc8716c
21 changed files with 6148 additions and 0 deletions
8
firmware/.clangd
Normal file
8
firmware/.clangd
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
CompileFlags:
|
||||||
|
Remove:
|
||||||
|
- "-mlongcalls"
|
||||||
|
- "-fno-shrink-wrap"
|
||||||
|
- "-fstrict-volatile-bitfields"
|
||||||
|
- "-fno-tree-switch-conversion"
|
||||||
|
- "-std=gnu17"
|
||||||
|
Compiler: xtensa-esp32-elf-g++
|
||||||
6
firmware/.gitignore
vendored
Normal file
6
firmware/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
|
.devcontainer
|
||||||
|
build
|
||||||
|
sdkconfig
|
||||||
|
sdkconfig.ci
|
||||||
6
firmware/CMakeLists.txt
Normal file
6
firmware/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# The following lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(candytuft-firmware)
|
||||||
53
firmware/README.md
Normal file
53
firmware/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux |
|
||||||
|
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
|
||||||
|
|
||||||
|
# Hello World Example
|
||||||
|
|
||||||
|
Starts a FreeRTOS task to print "Hello World".
|
||||||
|
|
||||||
|
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||||
|
|
||||||
|
## How to use example
|
||||||
|
|
||||||
|
Follow detailed instructions provided specifically for this example.
|
||||||
|
|
||||||
|
Select the instructions depending on Espressif chip installed on your development board:
|
||||||
|
|
||||||
|
- [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html)
|
||||||
|
- [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html)
|
||||||
|
|
||||||
|
|
||||||
|
## Example folder contents
|
||||||
|
|
||||||
|
The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main).
|
||||||
|
|
||||||
|
ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both).
|
||||||
|
|
||||||
|
Below is short explanation of remaining files in the project folder.
|
||||||
|
|
||||||
|
```
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── pytest_hello_world.py Python script used for automated testing
|
||||||
|
├── main
|
||||||
|
│ ├── CMakeLists.txt
|
||||||
|
│ └── hello_world_main.c
|
||||||
|
└── README.md This is the file you are currently reading
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* Program upload failure
|
||||||
|
|
||||||
|
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
|
||||||
|
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
|
||||||
|
|
||||||
|
## Technical support and feedback
|
||||||
|
|
||||||
|
Please use the following feedback channels:
|
||||||
|
|
||||||
|
* For technical queries, go to the [esp32.com](https://esp32.com/) forum
|
||||||
|
* For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues)
|
||||||
|
|
||||||
|
We will get back to you as soon as possible.
|
||||||
6
firmware/main/CMakeLists.txt
Normal file
6
firmware/main/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
idf_component_register(SRCS "src/hello_world_main.c"
|
||||||
|
"src/iic.c"
|
||||||
|
"src/lcd/ssd1306.c"
|
||||||
|
"src/ui/inputs.c"
|
||||||
|
"src/led/ws2811.c"
|
||||||
|
INCLUDE_DIRS "include")
|
||||||
68
firmware/main/Kconfig.projbuild
Normal file
68
firmware/main/Kconfig.projbuild
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
menu "Candytuft Pin Mapping"
|
||||||
|
menu "Inputs"
|
||||||
|
config PIN_WHEEL_A
|
||||||
|
int "Rotary encoder pin A"
|
||||||
|
range 0 39
|
||||||
|
default 21
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to pin A of the rotary encoder.
|
||||||
|
|
||||||
|
config PIN_WHEEL_B
|
||||||
|
int "Rotary encoder pin B"
|
||||||
|
range 0 39
|
||||||
|
default 22
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to pin B of the rotary encoder.
|
||||||
|
|
||||||
|
config KEY_FORWARD
|
||||||
|
int "Front Panel Forward Key Mapping"
|
||||||
|
range 0 39
|
||||||
|
default 4
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the Forward key.
|
||||||
|
|
||||||
|
config KEY_BACKSPACE
|
||||||
|
int "Front Panel Backspace Key Mapping"
|
||||||
|
range 0 39
|
||||||
|
default 13
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the Backspace key.
|
||||||
|
|
||||||
|
config KEY_DONE
|
||||||
|
int "Front Panel Done Key Mapping"
|
||||||
|
range 0 39
|
||||||
|
default 14
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the Done key.
|
||||||
|
|
||||||
|
config KEY_CANCEL
|
||||||
|
int "Front Panel Cancel Key Mapping"
|
||||||
|
range 0 39
|
||||||
|
default 18
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the Cancel key.
|
||||||
|
|
||||||
|
config KEY_OPTION
|
||||||
|
int "Front Panel Option Key Mapping"
|
||||||
|
range 0 39
|
||||||
|
default 19
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the Option key.
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "LCD"
|
||||||
|
config LCD_SCL
|
||||||
|
int "LCD panel SCL pin mapping"
|
||||||
|
range 0 39
|
||||||
|
default 25
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the LCD panel's SCL pin.
|
||||||
|
|
||||||
|
config LCD_SDA
|
||||||
|
int "LCD panel SDA pin mapping"
|
||||||
|
range 0 39
|
||||||
|
default 26
|
||||||
|
help
|
||||||
|
If using a non-standard mainboard, set this value to the GPIO connected to the LCD panel's SDA pin.
|
||||||
|
endmenu
|
||||||
|
endmenu
|
||||||
0
firmware/main/component.mk
Normal file
0
firmware/main/component.mk
Normal file
23
firmware/main/include/iic.h
Normal file
23
firmware/main/include/iic.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* MX-008 "Onyx Sovereign" -02 "Kingmaker"
|
||||||
|
* ============================================================================
|
||||||
|
* === IIC interface configuration
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Alexis Maya-Isabelle Shuping
|
||||||
|
* 29th March 2021
|
||||||
|
* University of Florida
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
|
||||||
|
#define T_IIC_INIT ("IIC INIT")
|
||||||
|
#define T_IIC_LCD ("IICL")
|
||||||
|
|
||||||
|
#define IIC_NUM_LCD (I2C_NUM_0)
|
||||||
|
|
||||||
|
void iicInit(i2c_port_t port, int sclNum, int sdaNum, bool pullUps, int clkRate);
|
||||||
|
|
||||||
|
void iicCommand(i2c_port_t port, uint_fast8_t targetAddress, uint_fast8_t toSend);
|
||||||
|
void iicData(i2c_port_t port, uint_fast8_t targetAddress, uint_fast8_t toSend);
|
||||||
9
firmware/main/include/lcd/FontBase.h
Normal file
9
firmware/main/include/lcd/FontBase.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct font_t {
|
||||||
|
const char* fTable;
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
uint16_t cSize;
|
||||||
|
} font_t;
|
||||||
1756
firmware/main/include/lcd/FontComicSansMS16.h
Normal file
1756
firmware/main/include/lcd/FontComicSansMS16.h
Normal file
File diff suppressed because it is too large
Load diff
1351
firmware/main/include/lcd/FontLiberationMono12.h
Normal file
1351
firmware/main/include/lcd/FontLiberationMono12.h
Normal file
File diff suppressed because it is too large
Load diff
1748
firmware/main/include/lcd/SymbolsFont.h
Normal file
1748
firmware/main/include/lcd/SymbolsFont.h
Normal file
File diff suppressed because it is too large
Load diff
97
firmware/main/include/lcd/ssd1306.h
Normal file
97
firmware/main/include/lcd/ssd1306.h
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* === SSD1306 128x64 IIC LCD Interface
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Provides a simple interface for rendering text to an SSD1306 LCD.
|
||||||
|
*
|
||||||
|
* digimint
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "FontComicSansMS16.h"
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// LCD target address
|
||||||
|
#define IIC_LCD_ADDR (0x3C)
|
||||||
|
|
||||||
|
#define LCD_TAG "LCD"
|
||||||
|
|
||||||
|
extern uint8_t LCD_data[8][128];
|
||||||
|
extern bool LCD_rowChanged[8];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the LCD interface.
|
||||||
|
*
|
||||||
|
* This command configures the IIC bus, sends the proper initialization commands
|
||||||
|
* to the LCD, and spawns a task to update the display regularly.
|
||||||
|
*
|
||||||
|
* @param updateTaskHandle If this variable is not NULL, a handle to the display
|
||||||
|
* update task will be stored in it.
|
||||||
|
*/
|
||||||
|
void LCD_init(TaskHandle_t* updateTaskHandle);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single character to the LCD buffer.
|
||||||
|
*
|
||||||
|
* @warning This function does not perform bounds checking.
|
||||||
|
*
|
||||||
|
* @param font The font to use for text rendering.
|
||||||
|
* @param oChr The character to render.
|
||||||
|
* @param x The x-coordinate for the top-left of the character.
|
||||||
|
* @param y The y-coordinate for the top-left of the character.
|
||||||
|
* @param invert Whether the character should be inverted before display.
|
||||||
|
*/
|
||||||
|
void LCD_renderChar(font_t font, const char oChr, uint16_t x, uint16_t y, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render text to the LCD buffer.
|
||||||
|
*
|
||||||
|
* @warning This function does not protect against buffer overrun.
|
||||||
|
*
|
||||||
|
* @param font The font to use for text rendering.
|
||||||
|
* @param str The text to render.
|
||||||
|
* @param x The x-coordinate for the top-left of the first character.
|
||||||
|
* @param y The y-coordinate for the top-left of the first character.
|
||||||
|
* @param invert Whether the character should be inverted before display.
|
||||||
|
*/
|
||||||
|
void LCD_renderText(font_t font, const char* str, uint16_t x, uint16_t y, bool invert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render text in Comic Sans
|
||||||
|
*
|
||||||
|
* A convenience macro that expands to
|
||||||
|
*
|
||||||
|
* `LCD_renderText(FontComicSansMS16, str, x, y);`
|
||||||
|
*/
|
||||||
|
static inline void LCD_renderTextCS(const char* str, uint16_t x, uint16_t y){
|
||||||
|
LCD_renderText(FontComicSansMS16, str, x, y, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually force an immediate update of the LCD.
|
||||||
|
*
|
||||||
|
* Note that this function will be called automatically by the LCD update task
|
||||||
|
* during normal operation, so it generally should not be manually invoked.
|
||||||
|
*
|
||||||
|
* A semaphore is used to ensure that only one task attempts to communicate with
|
||||||
|
* the display at a time.
|
||||||
|
*
|
||||||
|
* @param block If true, then the calling task will block until the LCD is
|
||||||
|
* available for communication. Otherwise, if the LCD is not immediately
|
||||||
|
* available, the function will simply return `false`.
|
||||||
|
*
|
||||||
|
* @return `true` if the LCD was successfully updated; `false` otherwise.
|
||||||
|
*
|
||||||
|
* @note This function will only return false if two conditions apply:
|
||||||
|
* (1) `block` is false; and
|
||||||
|
* (2) The LCD semaphore has been taken (i.e. the LCD is currently busy)
|
||||||
|
*/
|
||||||
|
bool LCD_updateDisplay(bool block);
|
||||||
28
firmware/main/include/led/ws2811.h
Normal file
28
firmware/main/include/led/ws2811.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define LED_PIN (23)
|
||||||
|
|
||||||
|
typedef enum LEDStripSpeed{
|
||||||
|
WS2811_LOW_SPEED = 10000000, // One tick == 0.1us
|
||||||
|
WS2811_HIGH_SPEED = 20000000 // One tick == 0.05us
|
||||||
|
} LEDStripSpeed_t;
|
||||||
|
|
||||||
|
typedef struct PACKED_ATTR rgbPacket {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
} rgbPacket_t;
|
||||||
|
|
||||||
|
typedef void (*ledRenderFunction_t)(
|
||||||
|
rgbPacket_t* buf,
|
||||||
|
size_t led_count,
|
||||||
|
unsigned long frame
|
||||||
|
);
|
||||||
|
|
||||||
|
extern uint8_t saturation;
|
||||||
|
|
||||||
|
void init_leds();
|
||||||
34
firmware/main/include/ui/inputs.h
Normal file
34
firmware/main/include/ui/inputs.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
|
||||||
|
#define INPUTS_TAG ("INPUT")
|
||||||
|
|
||||||
|
extern TaskHandle_t T_ReadInputs_Handle;
|
||||||
|
extern EventGroupHandle_t EV_InputEventGroupHandle;
|
||||||
|
|
||||||
|
typedef enum InputEventGroupFlags {
|
||||||
|
INPUT_EVENT_WHEEL = (1 << 0),
|
||||||
|
INPUT_EVENT_KEY_FORWARD = (1 << 1),
|
||||||
|
INPUT_EVENT_KEY_BACKSPACE = (1 << 2),
|
||||||
|
INPUT_EVENT_KEY_DONE = (1 << 3),
|
||||||
|
INPUT_EVENT_KEY_CANCEL = (1 << 4),
|
||||||
|
INPUT_EVENT_KEY_OPTION = (1 << 5)
|
||||||
|
} UIEventGroupFlags_t;
|
||||||
|
|
||||||
|
typedef struct InputState {
|
||||||
|
bool forward, backspace, done, cancel, option;
|
||||||
|
int wheel;
|
||||||
|
uint8_t _wheelQuadrant;
|
||||||
|
} InputState_t;
|
||||||
|
|
||||||
|
void init_inputs();
|
||||||
|
|
||||||
|
InputState_t waitForInputEvents(UIEventGroupFlags_t events, BaseType_t xWaitForAll, TickType_t xTicksToWait);
|
||||||
|
|
||||||
|
InputState_t getInputState();
|
||||||
163
firmware/main/src/hello_world_main.c
Normal file
163
firmware/main/src/hello_world_main.c
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: CC0-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "driver/i2c_types.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/projdefs.h"
|
||||||
|
#include "hal/gpio_types.h"
|
||||||
|
#include "hal/i2c_types.h"
|
||||||
|
// #include "portmacro.h"
|
||||||
|
// #include "portmacro.h"
|
||||||
|
// #include "portmacro.h"
|
||||||
|
// #include "portmacro.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_chip_info.h"
|
||||||
|
#include "esp_flash.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
|
||||||
|
#include "lcd/ssd1306.h"
|
||||||
|
#include "led/ws2811.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "soc/clk_tree_defs.h"
|
||||||
|
|
||||||
|
#include "ui/inputs.h"
|
||||||
|
|
||||||
|
void T_watchInputs(void* params){
|
||||||
|
while(1){
|
||||||
|
EventBits_t changed = xEventGroupWaitBits(
|
||||||
|
EV_InputEventGroupHandle,
|
||||||
|
(
|
||||||
|
INPUT_EVENT_KEY_BACKSPACE
|
||||||
|
| INPUT_EVENT_KEY_FORWARD
|
||||||
|
| INPUT_EVENT_KEY_CANCEL
|
||||||
|
| INPUT_EVENT_KEY_DONE
|
||||||
|
| INPUT_EVENT_KEY_OPTION
|
||||||
|
| INPUT_EVENT_WHEEL
|
||||||
|
),
|
||||||
|
pdTRUE,
|
||||||
|
pdFALSE,
|
||||||
|
portMAX_DELAY
|
||||||
|
);
|
||||||
|
|
||||||
|
InputState_t curInputs = getInputState();
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_KEY_FORWARD){
|
||||||
|
if(curInputs.forward){
|
||||||
|
ESP_LOGI("UI", "Forward Pressed");
|
||||||
|
}else{
|
||||||
|
ESP_LOGI("UI", "Forward Released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_KEY_BACKSPACE){
|
||||||
|
if(curInputs.backspace){
|
||||||
|
ESP_LOGI("UI", "Backspace Pressed");
|
||||||
|
}else{
|
||||||
|
ESP_LOGI("UI", "Backspace Released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_KEY_DONE){
|
||||||
|
if(curInputs.done){
|
||||||
|
ESP_LOGI("UI", "Done Pressed");
|
||||||
|
}else{
|
||||||
|
ESP_LOGI("UI", "Done Released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_KEY_CANCEL){
|
||||||
|
if(curInputs.cancel){
|
||||||
|
ESP_LOGI("UI", "Cancel Pressed");
|
||||||
|
}else{
|
||||||
|
ESP_LOGI("UI", "Cancel Released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_KEY_OPTION){
|
||||||
|
if(curInputs.option){
|
||||||
|
ESP_LOGI("UI", "Option Pressed");
|
||||||
|
}else{
|
||||||
|
ESP_LOGI("UI", "Option Released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed & INPUT_EVENT_WHEEL){
|
||||||
|
ESP_LOGI("UI", "Wheel: %d", curInputs.wheel);
|
||||||
|
if(curInputs.wheel < 0){
|
||||||
|
saturation = 0;
|
||||||
|
}else if(curInputs.wheel > 255){
|
||||||
|
saturation = 255;
|
||||||
|
}else{
|
||||||
|
saturation = (uint8_t)curInputs.wheel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
printf("Hello world! This is Candytuft 40M9K!\n");
|
||||||
|
|
||||||
|
LCD_init(NULL);
|
||||||
|
|
||||||
|
LCD_renderTextCS("Comic", 0, 0);
|
||||||
|
LCD_renderTextCS("Sans", 0, 16);
|
||||||
|
LCD_renderTextCS("LCD", 0, 32);
|
||||||
|
|
||||||
|
LCD_updateDisplay(true);
|
||||||
|
|
||||||
|
init_inputs();
|
||||||
|
init_leds();
|
||||||
|
|
||||||
|
|
||||||
|
xTaskCreate(T_watchInputs, "Watch inputs", 2048, NULL, 2, NULL);
|
||||||
|
|
||||||
|
/* Print chip information */
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
uint32_t flash_size;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
|
||||||
|
CONFIG_IDF_TARGET,
|
||||||
|
chip_info.cores,
|
||||||
|
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
||||||
|
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
||||||
|
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
||||||
|
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
|
||||||
|
|
||||||
|
unsigned major_rev = chip_info.revision / 100;
|
||||||
|
unsigned minor_rev = chip_info.revision % 100;
|
||||||
|
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
||||||
|
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||||
|
printf("Get flash size failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
|
||||||
|
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
|
||||||
|
|
||||||
|
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for (int i = 10; i >= 0; i--) {
|
||||||
|
// printf("Restarting in %d seconds...\n", i);
|
||||||
|
// vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
|
// }
|
||||||
|
// printf("Restarting now.\n");
|
||||||
|
// fflush(stdout);
|
||||||
|
// esp_restart();
|
||||||
|
}
|
||||||
57
firmware/main/src/iic.c
Normal file
57
firmware/main/src/iic.c
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* MX-008 "Onyx Sovereign" -02 "Kingmaker"
|
||||||
|
* ============================================================================
|
||||||
|
* === IIC interface configuration
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Alexis Maya-Isabelle Shuping
|
||||||
|
* 29th March 2021
|
||||||
|
* University of Florida
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "iic.h"
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void iicInit(i2c_port_t port, int sclNum, int sdaNum, bool pullUps, int clkRate){
|
||||||
|
ESP_LOGI(T_IIC_INIT, "Initializing LCD interface %d at %dHz (SCL pin %d; SDA pin %d; pullups %s)", (int)port, clkRate, sclNum, sdaNum, pullUps ? "enabled" : "disabled");
|
||||||
|
|
||||||
|
i2c_config_t my2c = (i2c_config_t) {
|
||||||
|
.mode = I2C_MODE_MASTER,
|
||||||
|
.sda_io_num = sdaNum,
|
||||||
|
.scl_io_num = sclNum,
|
||||||
|
.sda_pullup_en = pullUps ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||||
|
.scl_pullup_en = pullUps ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||||
|
.master.clk_speed = clkRate
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c_param_config(port, &my2c);
|
||||||
|
i2c_driver_install(port, my2c.mode, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iicCommand(i2c_port_t port, uint_fast8_t targetAddress, uint_fast8_t toSend){
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, targetAddress << 1 | 0, true));
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x00, true));
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, toSend, true));
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
ESP_ERROR_CHECK(i2c_master_cmd_begin(port, cmd, 1000 / portTICK_PERIOD_MS));
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iicData(i2c_port_t port, uint_fast8_t targetAddress, uint_fast8_t toSend){
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, targetAddress << 1 | 0, true));
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x40, true));
|
||||||
|
ESP_ERROR_CHECK(i2c_master_write_byte(cmd, toSend, true));
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
ESP_ERROR_CHECK(i2c_master_cmd_begin(port, cmd, 1000 / portTICK_PERIOD_MS));
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
}
|
||||||
189
firmware/main/src/lcd/ssd1306.c
Normal file
189
firmware/main/src/lcd/ssd1306.c
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* === SSD1306 128x64 IIC LCD Interface
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Provides a simple interface for rendering text to an SSD1306 LCD.
|
||||||
|
*
|
||||||
|
* digimint
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "iic.h"
|
||||||
|
#include "lcd/FontComicSansMS16.h"
|
||||||
|
#include "lcd/ssd1306.h"
|
||||||
|
#include "lcd/SymbolsFont.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/portmacro.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint8_t LCD_data[8][128];
|
||||||
|
bool LCD_rowChanged[8];
|
||||||
|
StaticSemaphore_t _lcSemaphore_buf;
|
||||||
|
|
||||||
|
volatile bool _lcDirty = false;
|
||||||
|
SemaphoreHandle_t _lcSemaphore = NULL;
|
||||||
|
|
||||||
|
static inline void _setPx(uint16_t x, uint16_t y){
|
||||||
|
uint16_t bank = y >> 3;
|
||||||
|
uint16_t posi = y & 0b111;
|
||||||
|
|
||||||
|
LCD_data[bank][x] |= (1 << posi);
|
||||||
|
LCD_rowChanged[bank] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void _clrPx(uint16_t x, uint16_t y){
|
||||||
|
uint16_t bank = y >> 3;
|
||||||
|
uint16_t posi = y & 0b111;
|
||||||
|
|
||||||
|
LCD_data[bank][x] &= ~(1 << posi);
|
||||||
|
LCD_rowChanged[bank] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD_renderChar(font_t font, const char oChr, uint16_t x, uint16_t y, bool invert){
|
||||||
|
char chr = oChr;
|
||||||
|
if(chr < ' ' || chr > '~'){
|
||||||
|
chr = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
chr -= ' ';
|
||||||
|
uint_fast16_t charsPerRow = ((font.width & 0b111) == 0) ? (font.width >> 3) : ((font.width >> 3) + 1);
|
||||||
|
const char* toRender = &(font.fTable)[chr*(font.cSize)];
|
||||||
|
for(uint16_t xx = 0; xx < font.width; ++xx){
|
||||||
|
for(uint16_t yy = 0; yy < font.height; ++yy){
|
||||||
|
if(
|
||||||
|
toRender[yy*charsPerRow + (xx >> 3)]
|
||||||
|
& (1 << (7-(xx & 0b111)))
|
||||||
|
){
|
||||||
|
invert ? _clrPx(xx+x, yy+y) : _setPx(xx+x, yy+y);
|
||||||
|
}else{
|
||||||
|
invert ? _setPx(xx+x, yy+y) : _clrPx(xx+x, yy+y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD_renderText(font_t font, const char* str, uint16_t x, uint16_t y, bool invert){
|
||||||
|
uint_fast16_t i = 0;
|
||||||
|
while(*str != 0){
|
||||||
|
LCD_renderChar(font, *str, font.width*i+x, y, invert);
|
||||||
|
++str;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD_updateDisplay(bool block){
|
||||||
|
if(!xSemaphoreTake(_lcSemaphore, 0)){
|
||||||
|
if(!block) return false;
|
||||||
|
else xSemaphoreTake(_lcSemaphore, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
for(uint32_t j = 0; j < 8; ++j){
|
||||||
|
// if(LCD_rowChanged[j] == false) continue;
|
||||||
|
for(uint32_t i = 0; i < 128; ++i){
|
||||||
|
iicData(IIC_NUM_LCD, IIC_LCD_ADDR, LCD_data[j][i]);
|
||||||
|
}
|
||||||
|
LCD_rowChanged[j] = false;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(_lcSemaphore);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void T_LCD_updateTask(void* params){
|
||||||
|
// while(1){
|
||||||
|
// Display_render();
|
||||||
|
// vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
// bool needsUpdate = false;
|
||||||
|
// for(uint8_t i = 0; i < 8; ++i) needsUpdate = needsUpdate || LCD_rowChanged[i];
|
||||||
|
// if(needsUpdate == true) LCD_updateDisplay(true);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void LCD_init(TaskHandle_t* updateTaskHandle){
|
||||||
|
ESP_LOGI(LCD_TAG, "Initializing SSD1306 LCD Interface...");
|
||||||
|
|
||||||
|
// Initialize the semaphore for LCD access.
|
||||||
|
_lcSemaphore = xSemaphoreCreateBinaryStatic(&_lcSemaphore_buf);
|
||||||
|
xSemaphoreGive(_lcSemaphore);
|
||||||
|
|
||||||
|
ESP_LOGD(LCD_TAG, " Initializing IIC");
|
||||||
|
iicInit(
|
||||||
|
IIC_NUM_LCD,
|
||||||
|
CONFIG_LCD_SCL,
|
||||||
|
CONFIG_LCD_SDA,
|
||||||
|
true,
|
||||||
|
200000
|
||||||
|
);
|
||||||
|
|
||||||
|
ESP_LOGD(LCD_TAG, " Sending LCD initialization commands");
|
||||||
|
|
||||||
|
/* Shouldn't be necessary to take the semaphore here, but better safe than
|
||||||
|
* sorry.
|
||||||
|
*/
|
||||||
|
if(!xSemaphoreTake(_lcSemaphore, 0)) esp_system_abort("Semaphore error during LCD initialization!");
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xAE);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xD5);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x80);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xA8);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x3F);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xD3);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x00);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x40);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x8D);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x14);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x20);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x00);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xA1);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xC8);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xDA);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x12);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x81);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xCF);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xD9);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xF1);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xDB);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x40);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xA4);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xA6);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0xAF);
|
||||||
|
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x00);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x10);
|
||||||
|
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x21);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x00);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x7F);
|
||||||
|
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x22);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x00);
|
||||||
|
iicCommand(IIC_NUM_LCD, IIC_LCD_ADDR, 0x07);
|
||||||
|
xSemaphoreGive(_lcSemaphore);
|
||||||
|
|
||||||
|
ESP_LOGD(LCD_TAG, " Initializing data buffer");
|
||||||
|
for(uint32_t j = 0; j < 8; ++j){
|
||||||
|
for(uint32_t i = 0; i < 128; ++i){
|
||||||
|
LCD_data[j][i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(LCD_TAG, " Clearing Display");
|
||||||
|
if(!LCD_updateDisplay(false)){
|
||||||
|
ESP_LOGW(LCD_TAG, "Unable to clear LCD display (communication semaphore already in use).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGD(LCD_TAG, " Spawning LCD update task");
|
||||||
|
// TaskHandle_t tsk;
|
||||||
|
// if(updateTaskHandle == NULL){
|
||||||
|
// updateTaskHandle = &tsk;
|
||||||
|
// }
|
||||||
|
// xTaskCreate(&T_LCD_updateTask, "LCD Update Task", 2048, NULL, 2, updateTaskHandle);
|
||||||
|
|
||||||
|
ESP_LOGI(LCD_TAG, "Ready.");
|
||||||
|
}
|
||||||
303
firmware/main/src/led/ws2811.c
Normal file
303
firmware/main/src/led/ws2811.c
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
#include <driver/rmt_encoder.h>
|
||||||
|
#include <driver/rmt_tx.h>
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include "led/ws2811.h"
|
||||||
|
#include "driver/rmt_common.h"
|
||||||
|
#include "driver/rmt_types.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "hal/rmt_types.h"
|
||||||
|
#include "soc/clk_tree_defs.h"
|
||||||
|
#include "soc/gpio_num.h"
|
||||||
|
|
||||||
|
#define SYMBOLS_PER_LED (8)
|
||||||
|
#define SYMBOLS_PER_PACKET (3*SYMBOLS_PER_LED)
|
||||||
|
|
||||||
|
uint8_t saturation = 255;
|
||||||
|
|
||||||
|
// The 'duration' here is in ticks - the actual time is controlled by the tick
|
||||||
|
// rate. In our case, low-speed == 10MHz tick rate; high-speed == 20MHz tick
|
||||||
|
// rate. Thus, a high-duration of 5 means 0.5us at low-speed and 0.25 at
|
||||||
|
// high-speed, which matches the datasheet.
|
||||||
|
const rmt_symbol_word_t ws2811_0 = {
|
||||||
|
.duration0 = 5,
|
||||||
|
.level0 = 1,
|
||||||
|
.duration1 = 20,
|
||||||
|
.level1 = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const rmt_symbol_word_t ws2811_1 = {
|
||||||
|
.duration0 = 12,
|
||||||
|
.level0 = 1,
|
||||||
|
.duration1 = 13,
|
||||||
|
.level1 = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const rmt_symbol_word_t ws2811_rst = {
|
||||||
|
.duration0 = 10,
|
||||||
|
.level0 = 1,
|
||||||
|
.duration1 = 202, // >= 51uS even at high-speed.
|
||||||
|
.level1 = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct _renderFunctionLL {
|
||||||
|
ledRenderFunction_t fn;
|
||||||
|
struct _renderFunctionLL* next;
|
||||||
|
} _renderFunctionLL_t;
|
||||||
|
|
||||||
|
typedef struct _ledTaskParams {
|
||||||
|
size_t ledCount;
|
||||||
|
} _ledTaskParams_t;
|
||||||
|
|
||||||
|
IRAM_ATTR size_t _encodeOne(
|
||||||
|
const rgbPacket_t* pkt,
|
||||||
|
unsigned int offset,
|
||||||
|
unsigned int write_limit,
|
||||||
|
rmt_symbol_word_t* to_write,
|
||||||
|
size_t buf_offset
|
||||||
|
){
|
||||||
|
if(offset > SYMBOLS_PER_PACKET){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t symbols_written = 0;
|
||||||
|
|
||||||
|
for(int i = offset; i < SYMBOLS_PER_PACKET; ++i){
|
||||||
|
if(symbols_written == write_limit) break;
|
||||||
|
|
||||||
|
if(i < SYMBOLS_PER_LED){
|
||||||
|
to_write[buf_offset + i] = (pkt -> r) & (1 << (7 - i)) ? ws2811_1 : ws2811_0;
|
||||||
|
}else if(i < 2*SYMBOLS_PER_LED){
|
||||||
|
unsigned int ii = i - SYMBOLS_PER_LED;
|
||||||
|
to_write[buf_offset + i] = (pkt -> g) & (1 << (7 - ii)) ? ws2811_1 : ws2811_0;
|
||||||
|
}else{
|
||||||
|
unsigned int ii = i - 2*SYMBOLS_PER_LED;
|
||||||
|
to_write[buf_offset + i] = (pkt -> b) & (1 << (7 - ii)) ? ws2811_1 : ws2811_0;
|
||||||
|
}
|
||||||
|
++symbols_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbols_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile int tmp = 0;
|
||||||
|
|
||||||
|
IRAM_ATTR size_t _encode(
|
||||||
|
const void* data,
|
||||||
|
size_t data_size,
|
||||||
|
size_t symbols_written,
|
||||||
|
size_t symbols_free,
|
||||||
|
rmt_symbol_word_t* symbol_buf,
|
||||||
|
bool* done,
|
||||||
|
void* arg
|
||||||
|
){
|
||||||
|
tmp = data_size;
|
||||||
|
if(symbols_free == 0) return 0; // Can't write anything.
|
||||||
|
|
||||||
|
const rgbPacket_t* packets = (rgbPacket_t*)data;
|
||||||
|
|
||||||
|
unsigned int packet_index = symbols_written / SYMBOLS_PER_PACKET;
|
||||||
|
unsigned int packet_offset = symbols_written % SYMBOLS_PER_PACKET;
|
||||||
|
unsigned int packet_count = data_size / sizeof(rgbPacket_t);
|
||||||
|
|
||||||
|
// ESP_LOGI("LED", "sz %lu", (unsigned long)data_size);
|
||||||
|
|
||||||
|
if(packet_index >= packet_count){
|
||||||
|
// Write the reset symbol and finish
|
||||||
|
symbol_buf[0] = ws2811_rst;
|
||||||
|
(*done) = true;
|
||||||
|
return 1;
|
||||||
|
}else{
|
||||||
|
if(symbols_free < 24) return 0;
|
||||||
|
(*done) = false;
|
||||||
|
return (size_t)_encodeOne(
|
||||||
|
&packets[packet_index],
|
||||||
|
/*packet_offset */ 0,
|
||||||
|
/* symbols_free */ 24,
|
||||||
|
symbol_buf,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rgbPacket_t fromHSV(uint8_t h, uint8_t s, uint8_t v){
|
||||||
|
double sd = (double)s / 255.0;
|
||||||
|
double vd = (double)v / 255.0;
|
||||||
|
double hd = (double)h * (360.0 / 255.0);
|
||||||
|
|
||||||
|
double max = vd;
|
||||||
|
double chroma = sd * vd;
|
||||||
|
double min = max - chroma;
|
||||||
|
|
||||||
|
double hPrime = hd >= 300.0 ? (hd - 360.0) / 60.0 : hd / 60.0;
|
||||||
|
|
||||||
|
double r, g, b;
|
||||||
|
|
||||||
|
if(hPrime < 1.0){
|
||||||
|
if(hPrime < 0.0){
|
||||||
|
r = max;
|
||||||
|
g = min;
|
||||||
|
b = min - hPrime * chroma;
|
||||||
|
}else{
|
||||||
|
r = max;
|
||||||
|
g = min + hPrime * chroma;
|
||||||
|
b = min;
|
||||||
|
}
|
||||||
|
}else if(hPrime < 3.0){
|
||||||
|
if(hPrime < 2.0){
|
||||||
|
r = min - (hPrime - 2.0) * chroma;
|
||||||
|
g = max;
|
||||||
|
b = min;
|
||||||
|
}else{
|
||||||
|
r = min;
|
||||||
|
g = max;
|
||||||
|
b = min + (hPrime - 2.0) * chroma;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(hPrime < 4.0){
|
||||||
|
r = min;
|
||||||
|
g = min - (hPrime - 4.0) * chroma;
|
||||||
|
b = max;
|
||||||
|
}else{
|
||||||
|
r = min + (hPrime - 4.0) * chroma;
|
||||||
|
g = min;
|
||||||
|
b = max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rgbPacket_t){
|
||||||
|
.r = (uint8_t)(r * 255.0),
|
||||||
|
.g = (uint8_t)(g * 255.0),
|
||||||
|
.b = (uint8_t)(b * 255.0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void _testRenderer(rgbPacket_t* buf, size_t led_count, unsigned long frame){
|
||||||
|
for(size_t i = 0; i < led_count; ++i){
|
||||||
|
buf[i] = fromHSV(
|
||||||
|
(uint8_t)(((frame + (unsigned long)i)*2) % 255UL),
|
||||||
|
saturation,
|
||||||
|
255
|
||||||
|
);
|
||||||
|
// buf[i] = (rgbPacket_t){
|
||||||
|
// .r = ((ii % 3UL) == 0UL) ? (uint8_t)255 : (uint8_t)0,
|
||||||
|
// .g = ((ii % 3UL) == 1UL) ? (uint8_t)255 : (uint8_t)0,
|
||||||
|
// .b = ((ii % 3UL) == 2UL) ? (uint8_t)255 : (uint8_t)0
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmt_channel_handle_t ledChannel;
|
||||||
|
rmt_encoder_handle_t ledEncoder;
|
||||||
|
TaskHandle_t ledTask;
|
||||||
|
|
||||||
|
void T_ledTask(void* params_raw){
|
||||||
|
_ledTaskParams_t params = *((_ledTaskParams_t*)params_raw);
|
||||||
|
|
||||||
|
|
||||||
|
rgbPacket_t* packetBufA = (rgbPacket_t*) malloc(
|
||||||
|
(params.ledCount)*sizeof(rgbPacket_t)
|
||||||
|
);
|
||||||
|
|
||||||
|
rgbPacket_t* packetBufB = (rgbPacket_t*) malloc(
|
||||||
|
(params.ledCount)*sizeof(rgbPacket_t)
|
||||||
|
);
|
||||||
|
|
||||||
|
const rmt_transmit_config_t myTxConfig = {
|
||||||
|
.loop_count = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned long frame = 0;
|
||||||
|
rgbPacket_t* active_buf = packetBufA;
|
||||||
|
rgbPacket_t* next_buf = packetBufB;
|
||||||
|
|
||||||
|
_testRenderer(
|
||||||
|
next_buf,
|
||||||
|
params.ledCount,
|
||||||
|
frame++
|
||||||
|
);
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
active_buf = next_buf;
|
||||||
|
next_buf = (active_buf == packetBufA) ? packetBufB : packetBufA;
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_transmit(
|
||||||
|
ledChannel,
|
||||||
|
ledEncoder,
|
||||||
|
active_buf,
|
||||||
|
(params.ledCount)*sizeof(rgbPacket_t),
|
||||||
|
&myTxConfig
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
_testRenderer(
|
||||||
|
next_buf,
|
||||||
|
params.ledCount,
|
||||||
|
frame++
|
||||||
|
);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_tx_wait_all_done(
|
||||||
|
ledChannel,
|
||||||
|
portMAX_DELAY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
vTaskDelay(20 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_leds(){
|
||||||
|
ESP_LOGI("LED", "Initializing RMT channel...");
|
||||||
|
rmt_tx_channel_config_t ledChannelCfg = {
|
||||||
|
.gpio_num = (gpio_num_t)LED_PIN,
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.resolution_hz = WS2811_HIGH_SPEED,
|
||||||
|
.mem_block_symbols = 128,
|
||||||
|
.trans_queue_depth = 4,
|
||||||
|
.intr_priority = 2
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_new_tx_channel(
|
||||||
|
&ledChannelCfg,
|
||||||
|
&ledChannel
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
ESP_LOGI("LED", "Configuring encoder...");
|
||||||
|
const rmt_simple_encoder_config_t ledCfg = {
|
||||||
|
.callback = _encode,
|
||||||
|
.min_chunk_size = 24,
|
||||||
|
.arg = NULL
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_new_simple_encoder(
|
||||||
|
&ledCfg,
|
||||||
|
&ledEncoder
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
_ledTaskParams_t params = {
|
||||||
|
.ledCount = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_LOGI("LED", "Activating RMT channel...");
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_enable(ledChannel)
|
||||||
|
);
|
||||||
|
|
||||||
|
ESP_LOGI("LED", "Spawning task...");
|
||||||
|
xTaskCreate(
|
||||||
|
T_ledTask,
|
||||||
|
"LED update task",
|
||||||
|
2048,
|
||||||
|
(void*)¶ms,
|
||||||
|
2,
|
||||||
|
&ledTask
|
||||||
|
);
|
||||||
|
ESP_LOGI("LED", "Done!");
|
||||||
|
}
|
||||||
240
firmware/main/src/ui/inputs.c
Normal file
240
firmware/main/src/ui/inputs.c
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
#include <freertos/projdefs.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "ui/inputs.h"
|
||||||
|
|
||||||
|
TaskHandle_t T_ReadInputs_Handle;
|
||||||
|
EventGroupHandle_t EV_InputEventGroupHandle;
|
||||||
|
SemaphoreHandle_t inputSemaphore;
|
||||||
|
|
||||||
|
StaticSemaphore_t _inputSemaphoreBuf;
|
||||||
|
StaticEventGroup_t _inputEventGroupBuf;
|
||||||
|
|
||||||
|
InputState_t _curInputState = {
|
||||||
|
.forward = false,
|
||||||
|
.backspace = false,
|
||||||
|
.done = false,
|
||||||
|
.cancel = false,
|
||||||
|
.option = false,
|
||||||
|
.wheel = 0,
|
||||||
|
._wheelQuadrant = 0b11
|
||||||
|
}; // NOTE: take inputSemaphore before accessing
|
||||||
|
|
||||||
|
typedef enum _InputEventType{
|
||||||
|
INPUT_EVENT_TYPE_KEY,
|
||||||
|
INPUT_EVENT_TYPE_WHEEL
|
||||||
|
} _InputEventType_t;
|
||||||
|
|
||||||
|
void IRAM_ATTR ISR_keyEvent(void* params){
|
||||||
|
BaseType_t higherTaskWoken = pdFALSE;
|
||||||
|
|
||||||
|
xTaskNotifyIndexedFromISR(
|
||||||
|
T_ReadInputs_Handle,
|
||||||
|
0,
|
||||||
|
INPUT_EVENT_TYPE_KEY,
|
||||||
|
eSetValueWithOverwrite,
|
||||||
|
&higherTaskWoken
|
||||||
|
);
|
||||||
|
|
||||||
|
if(higherTaskWoken){
|
||||||
|
portYIELD_FROM_ISR(higherTaskWoken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR ISR_wheelEvent(void* params){
|
||||||
|
BaseType_t higherTaskWoken = pdFALSE;
|
||||||
|
|
||||||
|
xTaskNotifyIndexedFromISR(
|
||||||
|
T_ReadInputs_Handle,
|
||||||
|
0,
|
||||||
|
INPUT_EVENT_TYPE_WHEEL,
|
||||||
|
eSetValueWithOverwrite,
|
||||||
|
&higherTaskWoken
|
||||||
|
);
|
||||||
|
|
||||||
|
if(higherTaskWoken){
|
||||||
|
portYIELD_FROM_ISR(higherTaskWoken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputState_t _getNewInputs(bool doDebounceDelay){
|
||||||
|
|
||||||
|
if(doDebounceDelay){
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreTake(inputSemaphore, portMAX_DELAY);
|
||||||
|
|
||||||
|
|
||||||
|
InputState_t newInputs = {
|
||||||
|
.forward = !gpio_get_level(CONFIG_KEY_FORWARD),
|
||||||
|
.backspace = !gpio_get_level(CONFIG_KEY_BACKSPACE),
|
||||||
|
.done = !gpio_get_level(CONFIG_KEY_DONE),
|
||||||
|
.cancel = !gpio_get_level(CONFIG_KEY_CANCEL),
|
||||||
|
.option = !gpio_get_level(CONFIG_KEY_OPTION),
|
||||||
|
.wheel = _curInputState.wheel,
|
||||||
|
._wheelQuadrant = (
|
||||||
|
(gpio_get_level(CONFIG_PIN_WHEEL_B) << 1)
|
||||||
|
| (gpio_get_level(CONFIG_PIN_WHEEL_A))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if(newInputs._wheelQuadrant == 0b11 && _curInputState._wheelQuadrant != 0b11){
|
||||||
|
if(_curInputState._wheelQuadrant == 0b01){
|
||||||
|
++newInputs.wheel;
|
||||||
|
}else if(_curInputState._wheelQuadrant == 0b10){
|
||||||
|
--newInputs.wheel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(inputSemaphore);
|
||||||
|
|
||||||
|
return newInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBits_t _calcInputDeltas(InputState_t newState){
|
||||||
|
EventBits_t deltas = 0;
|
||||||
|
|
||||||
|
xSemaphoreTake(inputSemaphore, portMAX_DELAY);
|
||||||
|
|
||||||
|
if(newState.forward != _curInputState.forward) deltas |= INPUT_EVENT_KEY_FORWARD;
|
||||||
|
if(newState.backspace != _curInputState.backspace) deltas |= INPUT_EVENT_KEY_BACKSPACE;
|
||||||
|
if(newState.done != _curInputState.done) deltas |= INPUT_EVENT_KEY_DONE;
|
||||||
|
if(newState.cancel != _curInputState.cancel) deltas |= INPUT_EVENT_KEY_CANCEL;
|
||||||
|
if(newState.option != _curInputState.option) deltas |= INPUT_EVENT_KEY_OPTION;
|
||||||
|
if(newState.wheel != _curInputState.wheel) deltas |= INPUT_EVENT_WHEEL;
|
||||||
|
|
||||||
|
xSemaphoreGive(inputSemaphore);
|
||||||
|
|
||||||
|
return deltas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that handles reading inputs, updating the internal state, and generating
|
||||||
|
* events in the EV_InputEventGroup.
|
||||||
|
*/
|
||||||
|
void T_ReadInputs(void* params){
|
||||||
|
while(1){
|
||||||
|
_InputEventType_t evt = (_InputEventType_t)ulTaskNotifyTakeIndexed(
|
||||||
|
0,
|
||||||
|
pdTRUE,
|
||||||
|
portMAX_DELAY
|
||||||
|
);
|
||||||
|
|
||||||
|
InputState_t newInputs = _getNewInputs(
|
||||||
|
evt == INPUT_EVENT_TYPE_KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
EventBits_t deltas = _calcInputDeltas(newInputs);
|
||||||
|
|
||||||
|
xSemaphoreTake(inputSemaphore, portMAX_DELAY);
|
||||||
|
_curInputState = newInputs;
|
||||||
|
xSemaphoreGive(inputSemaphore);
|
||||||
|
|
||||||
|
xEventGroupSetBits(EV_InputEventGroupHandle, deltas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task to ensure that all event-bits are cleared immediately.
|
||||||
|
*
|
||||||
|
* If a task starts listening on a UI event, it should *not* receive any UI
|
||||||
|
* event sent before it started listening. Thus, UI events should be cleared
|
||||||
|
* immediately. This task perpetually waits for any UI event and clears it.
|
||||||
|
*/
|
||||||
|
void T_EnsureInputEGClear(void* params){
|
||||||
|
while(1){
|
||||||
|
xEventGroupWaitBits(
|
||||||
|
EV_InputEventGroupHandle,
|
||||||
|
0xf,
|
||||||
|
pdTRUE,
|
||||||
|
pdFALSE,
|
||||||
|
portMAX_DELAY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_inputs(){
|
||||||
|
ESP_LOGI(INPUTS_TAG, "Configuring input GPIOs...");
|
||||||
|
|
||||||
|
gpio_config_t _wheelConfig = {
|
||||||
|
.pin_bit_mask = (
|
||||||
|
(1ULL << CONFIG_PIN_WHEEL_A)
|
||||||
|
| (1ULL << CONFIG_PIN_WHEEL_B)
|
||||||
|
),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_POSEDGE
|
||||||
|
};
|
||||||
|
|
||||||
|
gpio_config_t _keyConfig = {
|
||||||
|
.pin_bit_mask = (
|
||||||
|
(1ULL << CONFIG_KEY_FORWARD)
|
||||||
|
| (1ULL << CONFIG_KEY_BACKSPACE)
|
||||||
|
| (1ULL << CONFIG_KEY_DONE)
|
||||||
|
| (1ULL << CONFIG_KEY_CANCEL)
|
||||||
|
| (1ULL << CONFIG_KEY_OPTION)
|
||||||
|
),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_ANYEDGE
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_config(&_wheelConfig));
|
||||||
|
ESP_ERROR_CHECK(gpio_config(&_keyConfig));
|
||||||
|
|
||||||
|
ESP_LOGI(INPUTS_TAG, "Creating event queue...");
|
||||||
|
EV_InputEventGroupHandle = xEventGroupCreateStatic(&_inputEventGroupBuf);
|
||||||
|
inputSemaphore = xSemaphoreCreateBinaryStatic(&_inputSemaphoreBuf);
|
||||||
|
xSemaphoreGive(inputSemaphore); // Starting value of 1
|
||||||
|
|
||||||
|
ESP_LOGI(INPUTS_TAG, "Configuring GPIO interrupts...");
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_KEY_FORWARD, ISR_keyEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_KEY_BACKSPACE, ISR_keyEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_KEY_DONE, ISR_keyEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_KEY_CANCEL, ISR_keyEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_KEY_OPTION, ISR_keyEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_PIN_WHEEL_A, ISR_wheelEvent, NULL));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CONFIG_PIN_WHEEL_B, ISR_wheelEvent, NULL));
|
||||||
|
|
||||||
|
ESP_LOGI(INPUTS_TAG, "Spawning tasks...");
|
||||||
|
xTaskCreate(T_EnsureInputEGClear, "Keeps Input EG Clear", 2048, NULL, 2, NULL);
|
||||||
|
xTaskCreate(T_ReadInputs, "Reads front-panel inputs", 2048, NULL, 4, &T_ReadInputs_Handle);
|
||||||
|
|
||||||
|
ESP_LOGI(INPUTS_TAG, "Complete!");
|
||||||
|
}
|
||||||
|
|
||||||
|
InputState_t waitForInputEvents(UIEventGroupFlags_t events, BaseType_t xWaitForAll, TickType_t xTicksToWait){
|
||||||
|
xEventGroupWaitBits(
|
||||||
|
EV_InputEventGroupHandle,
|
||||||
|
events,
|
||||||
|
pdTRUE,
|
||||||
|
xWaitForAll,
|
||||||
|
xTicksToWait
|
||||||
|
);
|
||||||
|
|
||||||
|
return getInputState();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputState_t getInputState(){
|
||||||
|
xSemaphoreTake(inputSemaphore, portMAX_DELAY);
|
||||||
|
InputState_t curInputs = _curInputState;
|
||||||
|
xSemaphoreGive(inputSemaphore);
|
||||||
|
|
||||||
|
return curInputs;
|
||||||
|
}
|
||||||
3
firmware/sdkconfig.defaults
Normal file
3
firmware/sdkconfig.defaults
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||||
|
# Espressif IoT Development Framework (ESP-IDF) 5.3.1 Project Minimal Configuration
|
||||||
|
#
|
||||||
Loading…
Add table
Add a link
Reference in a new issue