修复了屏幕颜色异常问题

This commit is contained in:
CSBOOK\44744
2026-06-26 21:03:55 +08:00
parent e56b0f197f
commit 7d915e6fb0
17 changed files with 324 additions and 44 deletions
@@ -113,9 +113,9 @@ static const control_plane_key_config_t s_key_configs[] = {
.long_press_ms = CONFIG_TQ_KEY_LONG_PRESS_MS,
.double_click_ms = CONFIG_TQ_KEY_DOUBLE_CLICK_MS,
.emit_press_release = true,
.enable_short_press = false,
.enable_short_press = true,
.enable_long_press = false,
.enable_double_click = false,
.enable_double_click = true,
},
{
.key_id = CONTROL_PLANE_KEY_ID_SW2,
@@ -31,6 +31,7 @@
#endif
#define CONTROL_PLANE_PTT_HOLD_STEP_MS 80
#define CONTROL_PLANE_PTT_HOLD_START_DELAY_MS 320
#define CONTROL_PLANE_PTT_HOLD_TASK_STACK 4096
#define CONTROL_PLANE_PTT_HOLD_TASK_PRIORITY 4
@@ -117,6 +118,14 @@ static void control_plane_ptt_hold_task(void *arg)
(void)arg;
ESP_LOGI(TAG, "PTT hold task started");
bool start_requested = false;
uint32_t waited_ms = 0;
while (s_ptt_hold_active &&
!s_stop_requested &&
waited_ms < CONTROL_PLANE_PTT_HOLD_START_DELAY_MS) {
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PTT_HOLD_STEP_MS));
waited_ms += CONTROL_PLANE_PTT_HOLD_STEP_MS;
}
while (s_ptt_hold_active && !s_stop_requested) {
if (!control_plane_is_sta_connected()) {
@@ -223,6 +232,18 @@ static void control_plane_dispatch_key_event(const control_plane_key_event_t *ev
break;
}
case CONTROL_PLANE_KEY_EVENT_DOUBLE_CLICK: {
const domain_expression_request_t request = {
.id = DOMAIN_EXPRESSION_MUYU,
.trigger = DOMAIN_EXPRESSION_TRIGGER_PTT_DOUBLE_CLICK,
};
esp_err_t expr_err = domain_runtime_request_expression(&request);
if (expr_err != ESP_OK && expr_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "PTT double-click expression failed: %s", esp_err_to_name(expr_err));
}
break;
}
default:
break;
}
+2
View File
@@ -87,6 +87,7 @@ typedef enum {
DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
DOMAIN_EXPRESSION_WEALTH_SPIN,
DOMAIN_EXPRESSION_WEALTH_DIRECTION,
DOMAIN_EXPRESSION_MUYU,
} domain_expression_id_t;
typedef enum {
@@ -98,6 +99,7 @@ typedef enum {
DOMAIN_EXPRESSION_TRIGGER_WIFI_RECONNECTED,
DOMAIN_EXPRESSION_TRIGGER_MODEL_DIRECTION,
DOMAIN_EXPRESSION_TRIGGER_DAILY_FORTUNE,
DOMAIN_EXPRESSION_TRIGGER_PTT_DOUBLE_CLICK,
} domain_expression_trigger_t;
typedef struct {
@@ -75,6 +75,8 @@ static const char *TAG = "domain_rt_adapters";
static const uint8_t s_server_error_frame_order[] = {0, 1, 2, 1, 0};
static const uint8_t s_wifi_disconnected_frame_order[] = {0, 1, 2, 3, 2, 1, 0};
static const uint8_t s_basic_face_frame_order[] = {0, 1, 2};
static const uint8_t s_muyu_frame_order[] = {0, 1, 2, 1};
/*
* Wealth-direction reveal asset directories, indexed parallel to the direction
@@ -119,10 +121,15 @@ static const domain_expression_catalog_entry_t s_expression_catalog[] = {
{
.id = DOMAIN_EXPRESSION_IDLE,
.platform_request = {
.kind = PLATFORM_LCD_PRESENTATION_IDLE,
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
.priority = 120,
.interrupt_current = true,
.return_to_idle = false,
.asset_dir = "jichubiaoqing",
.frame_order = s_basic_face_frame_order,
.frame_order_count = sizeof(s_basic_face_frame_order) / sizeof(s_basic_face_frame_order[0]),
.repeat_count = 0,
.frame_ms = 100,
},
},
{
@@ -175,6 +182,21 @@ static const domain_expression_catalog_entry_t s_expression_catalog[] = {
.frame_ms = DOMAIN_WEALTH_DIRECTION_FRAME_MS,
},
},
{
.id = DOMAIN_EXPRESSION_MUYU,
.platform_request = {
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
.priority = 125,
.interrupt_current = true,
.return_to_idle = true,
.asset_dir = "muyu",
.frame_order = s_muyu_frame_order,
.frame_order_count = sizeof(s_muyu_frame_order) / sizeof(s_muyu_frame_order[0]),
.repeat_count = 25,
.frame_ms = 50,
.duration_ms = 10000,
},
},
};
static void snapshot_domain_wifi_behavior_hooks(domain_wifi_behavior_hooks_t *out_hooks)
+13
View File
@@ -63,6 +63,19 @@ target_add_binary_data(${COMPONENT_LIB}
BINARY
)
foreach(lcd_rle_asset
jichubiaoqing0
jichubiaoqing1
jichubiaoqing2
muyu0
muyu1
muyu2)
target_add_binary_data(${COMPONENT_LIB}
"assets/lcd/${lcd_rle_asset}.rle"
BINARY
)
endforeach()
set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts")
set(platform_asset_image_dir "${CMAKE_CURRENT_BINARY_DIR}/asset_image")
set(platform_asset_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/asset_image.stamp")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

+1
View File
@@ -361,6 +361,7 @@ typedef struct {
size_t frame_order_count;
uint16_t repeat_count;
uint32_t frame_ms;
uint32_t duration_ms;
} platform_lcd_presentation_request_t;
/**
@@ -34,6 +34,7 @@ typedef struct {
#define LCD_DIRECT_DEFAULT_ICON_COUNT 5U
const lcd_direct_image_t *lcd_direct_default_image(void);
const lcd_direct_image_t *lcd_direct_asset_frame(const char *asset_dir, uint8_t frame_index);
const lcd_direct_icon_t *lcd_direct_icon(uint16_t icon_id);
const lcd_direct_icon_draw_t *lcd_direct_default_icon_sequence(size_t *out_count);
+198 -38
View File
@@ -33,7 +33,6 @@
#define LCD_CMD_MADCTL 0x36
#define LCD_CMD_RAMCTRL 0xB0
#define LCD_RAMCTRL_LE_BIT (1U << 3)
static const char *TAG = "lcd_direct";
@@ -43,8 +42,11 @@ typedef struct {
bool interrupt_current;
bool return_to_idle;
char asset_dir[PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX];
uint8_t frame_order[PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX];
size_t frame_order_count;
uint16_t repeat_count;
uint32_t frame_ms;
uint32_t duration_ms;
} lcd_direct_request_t;
typedef struct {
@@ -84,6 +86,8 @@ static uint32_t s_completion_count;
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER];
static void lcd_direct_signal_completion(void);
static uint16_t lcd_direct_swap_color(uint16_t color)
{
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
@@ -166,11 +170,7 @@ static esp_err_t lcd_direct_panel_init(void)
#else
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
#endif
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
#else
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
#endif
.bits_per_pixel = 16,
};
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(s_panel_io, &panel_config, &s_panel),
@@ -207,9 +207,6 @@ static esp_err_t lcd_direct_panel_init(void)
madctl |= 0x08;
#endif
uint8_t ramctrl[] = {0x00, 0xF0};
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
ramctrl[1] |= LCD_RAMCTRL_LE_BIT;
#endif
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_MADCTL, &madctl, 1),
TAG,
"panel MADCTL failed");
@@ -333,25 +330,6 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
}
}
static void lcd_direct_overlay_boot_marker(uint16_t *pixel_buffer, uint16_t row_start, uint16_t row_count)
{
const uint16_t marker_color = lcd_direct_swap_color(0xFFFF);
const uint16_t row_end = row_start + row_count;
for (uint16_t y = row_start; y < row_end; ++y) {
uint16_t local_y = y - row_start;
for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) {
const bool border = y < 2U || y >= (LCD_DIRECT_HEIGHT - 2U) ||
x < 2U || x >= (LCD_DIRECT_WIDTH - 2U);
const bool center_line = (x >= 158U && x <= 161U && y >= 100U && y <= 140U) ||
(y >= 118U && y <= 121U && x >= 140U && x <= 180U);
if (border || center_line) {
pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + x] = marker_color;
}
}
}
}
static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
const lcd_direct_icon_draw_t *icons,
size_t icon_count)
@@ -399,7 +377,6 @@ static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
}
lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, icons, icon_count);
lcd_direct_overlay_boot_marker(s_line_buffer, row_start, row_count);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count),
TAG,
"write image pixels failed");
@@ -408,12 +385,169 @@ static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
return ESP_OK;
}
static esp_err_t lcd_direct_draw_default_image(void)
static esp_err_t lcd_direct_draw_asset_sequence_once(const lcd_direct_request_t *request)
{
size_t icon_count = 0;
const lcd_direct_icon_draw_t *icons = lcd_direct_default_icon_sequence(&icon_count);
ESP_LOGI(TAG, "Drawing default LCD image icons=%u", (unsigned)icon_count);
return lcd_direct_draw_rle_image(lcd_direct_default_image(), icons, icon_count);
ESP_RETURN_ON_FALSE(request != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid asset request");
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
for (size_t i = 0; i < frame_count; ++i) {
bool stop = false;
portENTER_CRITICAL(&s_state_lock);
stop = s_stop_requested || s_pending_valid;
portEXIT_CRITICAL(&s_state_lock);
if (stop) {
return ESP_ERR_INVALID_STATE;
}
const uint8_t frame_index = request->frame_order_count > 0U ? request->frame_order[i] : (uint8_t)i;
const lcd_direct_image_t *frame = lcd_direct_asset_frame(request->asset_dir, frame_index);
ESP_RETURN_ON_FALSE(frame != NULL,
ESP_ERR_NOT_FOUND,
TAG,
"LCD asset frame not found asset=%s frame=%u",
request->asset_dir,
(unsigned)frame_index);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
TAG,
"draw asset frame failed");
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
vTaskDelay(pdMS_TO_TICKS(frame_ms));
}
return ESP_OK;
}
static esp_err_t lcd_direct_draw_asset_sequence_for_duration(const lcd_direct_request_t *request,
TickType_t start_tick,
TickType_t duration_ticks)
{
ESP_RETURN_ON_FALSE(request != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid timed asset request");
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
while ((xTaskGetTickCount() - start_tick) < duration_ticks) {
for (size_t i = 0; i < frame_count; ++i) {
bool stop = false;
portENTER_CRITICAL(&s_state_lock);
stop = s_stop_requested || s_pending_valid;
portEXIT_CRITICAL(&s_state_lock);
if (stop) {
return ESP_ERR_INVALID_STATE;
}
const uint8_t frame_index = request->frame_order_count > 0U ? request->frame_order[i] : (uint8_t)i;
const lcd_direct_image_t *frame = lcd_direct_asset_frame(request->asset_dir, frame_index);
ESP_RETURN_ON_FALSE(frame != NULL,
ESP_ERR_NOT_FOUND,
TAG,
"LCD asset frame not found asset=%s frame=%u",
request->asset_dir,
(unsigned)frame_index);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
TAG,
"draw timed asset frame failed");
const TickType_t now = xTaskGetTickCount();
const TickType_t elapsed_ticks = now - start_tick;
if (elapsed_ticks >= duration_ticks) {
return ESP_OK;
}
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
const TickType_t frame_ticks = pdMS_TO_TICKS(frame_ms);
const TickType_t remaining_ticks = duration_ticks - elapsed_ticks;
vTaskDelay(frame_ticks < remaining_ticks ? frame_ticks : remaining_ticks);
}
}
return ESP_OK;
}
static esp_err_t lcd_direct_play_asset_sequence(const lcd_direct_request_t *request)
{
ESP_RETURN_ON_FALSE(request != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid asset sequence");
if (request->asset_dir[0] == '\0' ||
lcd_direct_asset_frame(request->asset_dir,
request->frame_order_count > 0U ? request->frame_order[0] : 0U) == NULL) {
ESP_LOGW(TAG, "LCD asset sequence unavailable, fallback to basic face asset=%s", request->asset_dir);
const lcd_direct_request_t fallback = {
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
.asset_dir = "jichubiaoqing",
.frame_order = {0, 1, 2},
.frame_order_count = 3,
.repeat_count = 1,
.frame_ms = 100,
};
esp_err_t fallback_err = lcd_direct_draw_asset_sequence_once(&fallback);
if (fallback_err == ESP_OK && request->repeat_count > 0U) {
lcd_direct_signal_completion();
}
return fallback_err;
}
if (request->duration_ms > 0U) {
const TickType_t start_tick = xTaskGetTickCount();
const TickType_t duration_ticks = pdMS_TO_TICKS(request->duration_ms);
esp_err_t err = lcd_direct_draw_asset_sequence_for_duration(request, start_tick, duration_ticks);
if (err != ESP_OK) {
return err;
}
lcd_direct_signal_completion();
return ESP_OK;
}
uint16_t remaining = request->repeat_count;
do {
esp_err_t err = lcd_direct_draw_asset_sequence_once(request);
if (err != ESP_OK) {
return err;
}
if (request->repeat_count > 0U) {
remaining--;
}
} while (request->repeat_count == 0U || remaining > 0U);
lcd_direct_signal_completion();
return ESP_OK;
}
static esp_err_t lcd_direct_play_basic_face_until_pending(void)
{
const lcd_direct_request_t request = {
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
.asset_dir = "jichubiaoqing",
.frame_order = {0, 1, 2},
.frame_order_count = 3,
.repeat_count = 0,
.frame_ms = 100,
};
while (true) {
bool stop = false;
portENTER_CRITICAL(&s_state_lock);
stop = s_stop_requested || s_pending_valid;
portEXIT_CRITICAL(&s_state_lock);
if (stop) {
return ESP_OK;
}
esp_err_t err = lcd_direct_draw_asset_sequence_once(&request);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
}
}
static void lcd_direct_signal_completion(void)
@@ -486,8 +620,6 @@ void lcd_direct_start_render_loop(void)
portEXIT_CRITICAL(&s_state_lock);
ESP_LOGI(TAG, "Direct LCD render loop started");
esp_err_t default_draw_err = lcd_direct_draw_default_image();
ESP_LOGI(TAG, "Direct LCD default image draw result=%s", esp_err_to_name(default_draw_err));
while (true) {
bool stop = false;
@@ -503,13 +635,33 @@ void lcd_direct_start_render_loop(void)
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
esp_err_t draw_err = ESP_OK;
if (request.kind == PLATFORM_LCD_PRESENTATION_IDLE) {
draw_err = lcd_direct_draw_default_image();
draw_err = lcd_direct_play_basic_face_until_pending();
} else if (request.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
draw_err = lcd_direct_play_asset_sequence(&request);
if (draw_err == ESP_OK && request.return_to_idle) {
lcd_direct_request_t idle = {
.kind = PLATFORM_LCD_PRESENTATION_IDLE,
};
portENTER_CRITICAL(&s_state_lock);
s_pending = idle;
s_pending_valid = true;
portEXIT_CRITICAL(&s_state_lock);
}
} else {
draw_err = lcd_direct_draw_test_pattern(&request);
}
xSemaphoreGive(s_lock);
if (draw_err == ESP_OK && request.repeat_count > 0U) {
lcd_direct_signal_completion();
if (draw_err != ESP_OK && draw_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "LCD presentation draw failed: %s", esp_err_to_name(draw_err));
}
}
} else {
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
esp_err_t idle_err = lcd_direct_play_basic_face_until_pending();
xSemaphoreGive(s_lock);
if (idle_err != ESP_OK && idle_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "LCD idle draw failed: %s", esp_err_to_name(idle_err));
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
@@ -587,10 +739,18 @@ esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_reques
.return_to_idle = request->return_to_idle,
.repeat_count = request->repeat_count,
.frame_ms = request->frame_ms,
.duration_ms = request->duration_ms,
};
if (request->asset_dir != NULL) {
strlcpy(copied.asset_dir, request->asset_dir, sizeof(copied.asset_dir));
}
if (request->frame_order != NULL && request->frame_order_count > 0U) {
copied.frame_order_count = request->frame_order_count;
if (copied.frame_order_count > PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX) {
copied.frame_order_count = PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX;
}
memcpy(copied.frame_order, request->frame_order, copied.frame_order_count);
}
TaskHandle_t task = NULL;
portENTER_CRITICAL(&s_state_lock);
+61 -1
View File
@@ -1,19 +1,79 @@
/* Generated from D:\Code\chaowan\lcd(1)\lcd\lcd\main\eye_image.c buyunqian1. */
#include "lcd_direct_assets.h"
#include <string.h>
extern const uint8_t _binary_buyunqian1_rle_start[] asm("_binary_buyunqian1_rle_start");
extern const uint8_t _binary_buyunqian1_rle_end[] asm("_binary_buyunqian1_rle_end");
extern const uint8_t _binary_jichubiaoqing0_rle_start[] asm("_binary_jichubiaoqing0_rle_start");
extern const uint8_t _binary_jichubiaoqing1_rle_start[] asm("_binary_jichubiaoqing1_rle_start");
extern const uint8_t _binary_jichubiaoqing2_rle_start[] asm("_binary_jichubiaoqing2_rle_start");
extern const uint8_t _binary_muyu0_rle_start[] asm("_binary_muyu0_rle_start");
extern const uint8_t _binary_muyu1_rle_start[] asm("_binary_muyu1_rle_start");
extern const uint8_t _binary_muyu2_rle_start[] asm("_binary_muyu2_rle_start");
static const lcd_direct_image_t s_default_image = {
.runs = (const lcd_direct_rle_run_t *)_binary_buyunqian1_rle_start,
.run_count = 72552U,
};
static const lcd_direct_image_t s_basic_face_frames[] = {
{
.runs = (const lcd_direct_rle_run_t *)_binary_jichubiaoqing0_rle_start,
.run_count = 5893U,
},
{
.runs = (const lcd_direct_rle_run_t *)_binary_jichubiaoqing1_rle_start,
.run_count = 4581U,
},
{
.runs = (const lcd_direct_rle_run_t *)_binary_jichubiaoqing2_rle_start,
.run_count = 3651U,
},
};
static const lcd_direct_image_t s_muyu_frames[] = {
{
.runs = (const lcd_direct_rle_run_t *)_binary_muyu0_rle_start,
.run_count = 63459U,
},
{
.runs = (const lcd_direct_rle_run_t *)_binary_muyu1_rle_start,
.run_count = 63665U,
},
{
.runs = (const lcd_direct_rle_run_t *)_binary_muyu2_rle_start,
.run_count = 63894U,
},
};
const lcd_direct_image_t *lcd_direct_default_image(void)
{
return &s_default_image;
}
const lcd_direct_image_t *lcd_direct_asset_frame(const char *asset_dir, uint8_t frame_index)
{
if (asset_dir == NULL) {
return NULL;
}
if (strcmp(asset_dir, "jichubiaoqing") == 0) {
if (frame_index < sizeof(s_basic_face_frames) / sizeof(s_basic_face_frames[0])) {
return &s_basic_face_frames[frame_index];
}
return NULL;
}
if (strcmp(asset_dir, "muyu") == 0) {
if (frame_index < sizeof(s_muyu_frames) / sizeof(s_muyu_frames[0])) {
return &s_muyu_frames[frame_index];
}
return NULL;
}
return NULL;
}
const lcd_direct_icon_t *lcd_direct_icon(uint16_t icon_id)
{
(void)icon_id;
+1 -1
View File
@@ -182,7 +182,7 @@ menu "Example Configuration"
config TQ_KEY_DOUBLE_CLICK_MS
int "Key double-click threshold (ms)"
default 300
default 450
range 100 1000
depends on TQ_KEY_INPUT_ENABLE
help
+1 -1
View File
@@ -30,7 +30,7 @@ CONFIG_TQ_KEY_IRQ_QUEUE_LEN=16
CONFIG_TQ_KEY_INPUT_POLL_MS=20
CONFIG_TQ_KEY_DEBOUNCE_INTERVAL_MS=20
CONFIG_TQ_KEY_LONG_PRESS_MS=800
CONFIG_TQ_KEY_DOUBLE_CLICK_MS=300
CONFIG_TQ_KEY_DOUBLE_CLICK_MS=450
# Internal DMA-capable SRAM budget. TLS/auth/fortune flows, hardware AES DMA
# bounce buffers, LCD DMA, and worker task stacks all need internal-only memory.