Multi screen support, with dyn alignment (#2801)

This commit is contained in:
Totoo
2025-10-03 19:10:10 +02:00
committed by GitHub
parent 23cabb8b8a
commit 371b6b5079
161 changed files with 4042 additions and 4157 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#pragma pack(push, 1)
struct bmp_header_t {
uint16_t signature;
+99 -10
View File
@@ -102,6 +102,56 @@ void lcd_init() {
// LCDs are configured for IM[2:0] = 001
// 8080-I system, 16-bit parallel bus
io.lcd_data_write_command_and_data(0xE0, {0x00, 0x09, 0x0C, 0x03, 0x10, 0x06, 0x34, 0x68,
0x49, 0x02, 0x0A, 0x07, 0x2C, 0x31, 0x0F});
io.lcd_data_write_command_and_data(0xE1, {0x00, 0x12, 0x15, 0x02, 0x10, 0x06, 0x35, 0x35,
0x4A, 0x05, 0x10, 0x0C, 0x2F, 0x33, 0x0F});
io.lcd_data_write_command_and_data(0xC0, {0x0F, 0x0F}); // Vreg1out=4.5 Vreg2out=-4.5
io.lcd_data_write_command_and_data(0xC1, {0x44}); // VGH = 5*VCI VGL = -4*VCI
io.lcd_data_write_command_and_data(0xC5, {0x00, 0x66, 0x80}); // VCOM
io.lcd_data_write_command_and_data(0x36, {
(1 << 7) | // MY=1
(0 << 6) | // MX=0
(0 << 5) | // MV=0
(1 << 4) | // ML=1: reverse vertical refresh to simplify scrolling logic
(1 << 3) // BGR=1: For Kingtech LCD, BGR filter.
});
// io.lcd_data_write_command_and_data(0x36, {0x48});
io.lcd_data_write_command_and_data(0x3A, {0x65}); // 0x55 was the original
// 这里的刷新率过低?
// io.lcd_data_write_command_and_data(0xB1, {0x40, 0x1F}); // fix 60 fps
io.lcd_data_write_command_and_data(0xB1, {0xA0, 0x11}); // Frame rate source is 11fps
io.lcd_data_write_command_and_data(0xB4, {0x02}); // 2 dot inversion
io.lcd_data_write_command_and_data(0xEE, {0x00, 0x04});
io.lcd_data_write_command_and_data(0xE9, {0x00});
io.lcd_data_write_command_and_data(0xF7, {0xA9, 0x51, 0x2C, 0x82});
io.lcd_data_write_command_and_data(0x21, {}); // Display Inversion On
io.lcd_data_write_command_and_data(0x11, {}); // Sleep Out
chThdSleepMilliseconds(120); // Delay 120ms
io.lcd_data_write_command_and_data(0x29, {}); // Display On
chThdSleepMilliseconds(50); // Delay 50ms
// Turn on Tearing Effect Line (TE) output signal.
io.lcd_data_write_command_and_data(0x35, {0b00000000});
}
void lcd_init_pp() {
// LCDs are configured for IM[2:0] = 001
// 8080-I system, 16-bit parallel bus
//
// 0x3a: DBI[2:0] = 101
// MDT[1:0] = XX (if not in 18-bit mode, right?)
@@ -281,6 +331,16 @@ void lcd_vertical_scrolling_start_address(
} // namespace
uint32_t ILI9341::lcd_read_display_id() {
io.lcd_data_write_command_and_data(0x04, {});
io.lcd_read_data_raw();
uint32_t value3 = io.lcd_read_data_raw();
uint32_t value4 = io.lcd_read_data_raw();
uint32_t value5 = io.lcd_read_data_raw();
return value5 + (value4 << 8) + (value3 << 16);
}
bool ILI9341::read_display_status() {
lcd_reset();
uint32_t display_status = lcd_read_display_status();
@@ -302,7 +362,32 @@ bool ILI9341::read_display_status() {
void ILI9341::init() {
lcd_reset();
lcd_init();
bool hpp = false;
// detect method 1
uint32_t id = lcd_read_display_id();
if (id == 5537894) hpp = true;
// detect method 2 - not working!
if (!hpp) {
draw_pixel({318, 478}, ui::Color::red());
fill_rectangle_unrolled8({0, 0, 240, 320}, ui::Color::black());
std::vector<ui::ColorRGB888> checker(1);
read_pixels({318, 478, 1, 1}, checker.data(), 1);
if (checker[0].b <= 10 && checker[0].g <= 10 && checker[0].r >= 100)
hpp = true;
}
// init screen
if (hpp) {
device_type = DEV_PORTARF;
lcd_init();
screen_width = 320;
screen_height = 480;
} else {
device_type = DEV_PORTAPACK;
lcd_init_pp();
screen_width = 240;
screen_height = 320;
}
}
void ILI9341::shutdown() {
@@ -335,7 +420,7 @@ void ILI9341::fill_rectangle_unrolled8(ui::Rect r, const ui::Color c) {
}
}
void ILI9341::render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer) {
void ILI9341::render_line(const ui::Point p, const uint16_t count, const ui::Color* line_buffer) {
lcd_start_ram_write(p, {count, 1});
io.lcd_write_pixels(line_buffer, count);
}
@@ -473,8 +558,8 @@ bool ILI9341::draw_bmp_from_sdcard_file(const ui::Point p, const std::filesystem
bmp_header_t bmp_header;
uint8_t type = 0;
char buffer[257];
ui::Color line_buffer[240];
ui::Color line_buffer[320];
int16_t start_x = p.x();
auto result = bmpimage.open(file);
if (result.is_valid())
return false;
@@ -508,8 +593,8 @@ bool ILI9341::draw_bmp_from_sdcard_file(const ui::Point p, const std::filesystem
width = bmp_header.width;
height = bmp_header.height;
if (width != 240)
start_x = (screen_width - start_x - width) / 2 + start_x; // center horizontally
if (width > screen_width || width > 320)
return false;
file_pos = bmp_header.image_data;
@@ -560,7 +645,7 @@ bool ILI9341::draw_bmp_from_sdcard_file(const ui::Point p, const std::filesystem
if (read_size.value() != 256)
break;
}
render_line({p.x(), p.y() + py}, px, line_buffer);
render_line({start_x, p.y() + py}, px, line_buffer);
px = 0;
py--;
@@ -668,7 +753,7 @@ void ILI9341::draw_bitmap(
for (size_t i = 0; i < count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
if (pixel) {
draw_pixel(ui::Point(x, y), foreground);
if (x <= screen_width && y <= screen_height) draw_pixel(ui::Point(x, y), foreground);
}
// move to next px
x++;
@@ -740,8 +825,12 @@ ui::Coord ILI9341::scroll_area_y(const ui::Coord y) const {
}
void ILI9341::scroll_disable() {
lcd_vertical_scrolling_definition(0, height(), 0);
lcd_vertical_scrolling_start_address(0);
if (device_type == DEV_PORTAPACK) {
lcd_vertical_scrolling_definition(0, height(), 0);
lcd_vertical_scrolling_start_address(0);
} else {
io.lcd_data_write_command_and_data(0x13, {}); // normal mode
}
}
} /* namespace lcd */
+2 -2
View File
@@ -43,6 +43,7 @@ class ILI9341 {
void operator=(const ILI9341&) = delete;
bool read_display_status();
uint32_t lcd_read_display_id();
void init();
void shutdown();
@@ -62,7 +63,7 @@ class ILI9341 {
void draw_pixel(const ui::Point p, const ui::Color color);
void draw_bmp_from_bmp_hex_arr(const ui::Point p, const uint8_t* bitmap, const uint8_t* transparency_color);
bool draw_bmp_from_sdcard_file(const ui::Point p, const std::filesystem::path& file);
void render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer);
void render_line(const ui::Point p, const uint16_t count, const ui::Color* line_buffer);
void render_box(const ui::Point p, const ui::Size s, const ui::Color* line_buffer);
template <size_t N>
@@ -163,7 +164,6 @@ class ILI9341 {
ui::Dim height;
ui::Coord current_position;
};
scroll_t scroll_state;
};
+36
View File
@@ -34,6 +34,42 @@ using namespace lpc43xx;
namespace portapack {
DeviceType device_type = DEV_PORTAPACK;
void IO::lcd_read_bytes(uint8_t* byte, size_t byte_count) {
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK) {
size_t word_count = byte_count / 2;
while (word_count) {
const auto word = lcd_read_data();
*(byte++) = word >> 8;
*(byte++) = word >> 0;
word_count--;
}
if (byte_count & 1) {
const auto word = lcd_read_data();
*(byte++) = word >> 8;
}
return;
}
// prf
//--dummy read:
dir_read();
lcd_rd_assert();
halPolledDelay(71);
data_read();
lcd_rd_deassert();
size_t word_count = byte_count / 3;
for (size_t i = 0; i < word_count; i++) {
uint32_t word = lcd_read_data(); // reads 3 byte of data
uint8_t r = ((word >> 16) & 0xff);
uint8_t g = ((word >> 8) & 0xff);
uint8_t b = ((word >> 0) & 0xff);
*(byte++) = r;
*(byte++) = g << 2;
*(byte++) = b;
}
}
void IO::init() {
data_mask_set();
data_write_high(0);
+62 -29
View File
@@ -52,6 +52,12 @@ static const uint16_t darken_mask[4] = {
namespace portapack {
enum DeviceType {
DEV_PORTAPACK,
DEV_PORTARF
};
extern DeviceType device_type;
class IO {
public:
enum class TouchPinsConfig : uint8_t {
@@ -140,12 +146,43 @@ class IO {
lcd_write_data(d);
}
}
uint32_t lcd_read_data_raw() {
// NOTE: Assumes ADDR=1 from command phase.
dir_read();
/* Start read operation */
lcd_rd_assert();
/* Wait for passthrough data(15:8) to settle -- ~16ns (3 cycles) typical */
/* Wait for read control L duration (355ns) */
halPolledDelay(71); // 355ns
const auto value_high = data_read();
/* Latch data[7:0] */
lcd_rd_deassert();
/* Wait for latched data[7:0] to settle -- ~26ns (5 cycles) typical */
/* Wait for read control H duration (90ns) */
halPolledDelay(18); // 90ns
const auto value_low = data_read();
uint32_t original_value = (value_high << 8) | value_low;
return original_value;
}
void lcd_data_read_command_and_data(
const uint_fast8_t command,
uint16_t* const data,
const size_t data_count) {
lcd_command(command);
if (device_type == DEV_PORTARF) {
// dummy read
dir_read();
lcd_rd_assert();
halPolledDelay(71);
data_read();
lcd_rd_deassert();
halPolledDelay(71);
}
for (size_t i = 0; i < data_count; i++) {
data[i] = lcd_read_data();
}
@@ -205,19 +242,7 @@ class IO {
}
}
void lcd_read_bytes(uint8_t* byte, size_t byte_count) {
size_t word_count = byte_count / 2;
while (word_count) {
const auto word = lcd_read_data();
*(byte++) = word >> 8;
*(byte++) = word >> 0;
word_count--;
}
if (byte_count & 1) {
const auto word = lcd_read_data();
*(byte++) = word >> 8;
}
}
void lcd_read_bytes(uint8_t* byte, size_t byte_count);
uint32_t io_read() {
io_stb_assert();
@@ -400,32 +425,40 @@ class IO {
}
uint32_t lcd_read_data() {
// NOTE: Assumes ADDR=1 from command phase.
dir_read();
/* Start read operation */
lcd_rd_assert();
/* Wait for passthrough data(15:8) to settle -- ~16ns (3 cycles) typical */
/* Wait for read control L duration (355ns) */
halPolledDelay(71); // 355ns
const auto value_high = data_read();
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK) {
const auto value_high = data_read();
/* Latch data[7:0] */
lcd_rd_deassert();
/* Wait for latched data[7:0] to settle -- ~26ns (5 cycles) typical */
/* Wait for read control H duration (90ns) */
halPolledDelay(71); // 90ns
const auto value_low = data_read();
uint32_t original_value = (value_high << 8) | value_low;
if (lcd_normally_black) return original_value;
if (dark_cover_enabled) {
// this is read data, so if the fake brightness is enabled AKA get_dark_cover() == true,
// then shift to back side AKA UNDARKENED_PIXEL, to prevent read shifted darkern info
original_value = UNDARKENED_PIXEL(original_value, brightness);
}
return original_value;
}
const auto value_high = data_read();
/* Latch data[7:0] */
lcd_rd_deassert();
/* Wait for latched data[7:0] to settle -- ~26ns (5 cycles) typical */
/* Wait for read control H duration (90ns) */
halPolledDelay(18); // 90ns
halPolledDelay(71);
const auto value_low = data_read();
uint32_t original_value = (value_high << 8) | value_low;
if (lcd_normally_black) return original_value;
if (dark_cover_enabled) {
// this is read data, so if the fake brightness is enabled AKA get_dark_cover() == true,
// then shift to back side AKA UNDARKENED_PIXEL, to prevent read shifted darkern info
original_value = UNDARKENED_PIXEL(original_value, brightness);
}
lcd_rd_deassert();
halPolledDelay(71);
const auto value_last_low = data_read();
uint32_t original_value = (value_high << 16) | value_low << 8 | value_last_low;
return original_value;
}
+4
View File
@@ -45,6 +45,8 @@ namespace ui {
#define UI_POS_X_RIGHT(charnum) ((int)(screen_width - ((charnum)*UI_POS_DEFAULT_WIDTH)))
// px position of the left character from the center of the screen (X) (for N character wide string)
#define UI_POS_X_CENTER(charnum) ((int)((screen_width / 2) - ((charnum)*UI_POS_DEFAULT_WIDTH / 2)))
// px position of the currcol in a table with colnum number of columns, where one coloumn is charnum characters wide maximum
#define UI_POS_X_TABLE(colnum, currcol) ((currcol) * (screen_width / (colnum)))
// px width of N characters
#define UI_POS_WIDTH(charnum) ((int)((charnum)*UI_POS_DEFAULT_WIDTH))
// px width of the screen
@@ -57,6 +59,8 @@ namespace ui {
#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum)*UI_POS_DEFAULT_HEIGHT)))
// remaining px from the charnum-th character to the right of the screen
#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum)*UI_POS_DEFAULT_WIDTH)))
// px width of the screen's percent
#define UI_POS_WIDTH_PERCENT(percent) ((int)(screen_width * (percent) / 100))
// px width of the screen
#define UI_POS_MAXHEIGHT (screen_height)
+59 -19
View File
@@ -534,7 +534,7 @@ void BigFrequency::paint(Painter& painter) {
_previous_frequency = _frequency;
rf::Frequency frequency{_frequency};
const auto rect = screen_rect();
const auto rect = screen_rect(); // why not use screen_rect() directly for width, ...? it may be too small, but ...
// Erase
painter.fill_rectangle(
@@ -544,7 +544,7 @@ void BigFrequency::paint(Painter& painter) {
// Prepare digits
if (!frequency) {
digits.fill(10); // ----.---
digit_pos = {0, rect.location().y()};
digit_pos = {(screen_width - ((7 * digit_width) + 8)) / 2, rect.location().y()};
} else {
frequency /= 1000; // GMMM.KKK(uuu)
@@ -561,7 +561,7 @@ void BigFrequency::paint(Painter& painter) {
break;
}
digit_pos = {(Coord)(240 - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
digit_pos = {(Coord)(screen_width - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
}
segment_color = style().foreground;
@@ -1334,24 +1334,57 @@ void NewButton::paint(Painter& painter) {
style.background);
int y = r.top();
if (bitmap_) {
int offset_y = vertical_center_ ? (r.height() / 2) - (bitmap_->size.height() / 2) : 6;
Point bmp_pos = {r.left() + (r.width() / 2) - (bitmap_->size.width() / 2), r.top() + offset_y};
y += bitmap_->size.height() - offset_y;
if (vertical_center_) {
const int bmp_h = bitmap_ ? bitmap_->size.height() : 0;
const int txt_h = !text_.empty() ? style.font.line_height() : 0;
int spacing = 0;
if (bmp_h > 0 && txt_h > 0) {
const int content_height = bmp_h + txt_h;
const int remaining_space = r.height() - content_height;
spacing = std::max(4, remaining_space / 3);
}
const int total_height = bmp_h + txt_h + spacing;
y += (r.height() - total_height) / 2;
painter.draw_bitmap(
bmp_pos,
*bitmap_,
color_,
style.background);
}
if (bitmap_) {
Point bmp_pos = {r.left() + (r.width() / 2) - (bitmap_->size.width() / 2), y};
y += bitmap_->size.height();
if (!text_.empty()) {
const auto label_r = style.font.size_of(text_);
painter.draw_string(
{r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2},
style,
text_);
painter.draw_bitmap(
bmp_pos,
*bitmap_,
color_,
style.background);
}
if (!text_.empty()) {
const auto label_r = style.font.size_of(text_);
if (bitmap_) {
y += spacing;
}
painter.draw_string(
{r.left() + (r.width() - label_r.width()) / 2, y},
style,
text_);
}
} else { // no valign
if (bitmap_) {
Point bmp_pos = {r.left() + (r.width() / 2) - (bitmap_->size.width() / 2), r.top() + 6};
y += bitmap_->size.height() - 6;
painter.draw_bitmap(
bmp_pos,
*bitmap_,
color_,
style.background);
}
if (!text_.empty()) {
const auto label_r = style.font.size_of(text_);
painter.draw_string(
{r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2},
style,
text_);
}
}
}
@@ -2722,6 +2755,13 @@ bool Waveform::on_touch(const TouchEvent event) {
}
}
void Waveform::set_data(int16_t* new_data) {
if (new_data != data_) {
data_ = new_data;
set_dirty();
}
}
void Waveform::paint(Painter& painter) {
// previously it's upside down , low level is up and high level is down, which doesn't make sense,
// if that was made for a reason, feel free to revert.
+2 -2
View File
@@ -989,7 +989,7 @@ class Waveform : public Widget {
void set_offset(const uint32_t new_offset);
void set_length(const uint32_t new_length);
void set_cursor(const uint32_t i, const int16_t position);
void set_data(int16_t* new_data);
bool is_paused() const;
void set_paused(bool paused);
bool is_clickable() const;
@@ -1110,7 +1110,7 @@ class OptionTabView : public View {
private:
Checkbox check_enable{
{2 * 8, 0 * 16},
{2 * 8, UI_POS_Y(0)},
20,
"",
false};
+5 -3
View File
@@ -127,9 +127,11 @@ struct is_flags_type {
template <typename TEnum>
constexpr bool is_flags_type_v = is_flags_type<TEnum>::value;
#define ENABLE_FLAGS_OPERATORS(type) \
template <> \
struct is_flags_type<type> { static constexpr bool value = true; };
#define ENABLE_FLAGS_OPERATORS(type) \
template <> \
struct is_flags_type<type> { \
static constexpr bool value = true; \
};
template <typename TEnum>
constexpr std::enable_if_t<is_flags_type_v<TEnum>, TEnum> operator|(TEnum a, TEnum b) {