mirror of
https://github.com/openmv/openmv.git
synced 2025-09-26 23:09:13 +08:00
995 lines
38 KiB
C
995 lines
38 KiB
C
/*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2013-2024 OpenMV, LLC.
|
|
* Copyright (c) 2019-2019 Kaizhi Wong <kidswong999@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* TV Python module.
|
|
*/
|
|
#include "omv_boardconfig.h"
|
|
|
|
#if MICROPY_PY_TV
|
|
|
|
#include "py/obj.h"
|
|
#include "py/nlr.h"
|
|
#include "py/mphal.h"
|
|
#include "py/runtime.h"
|
|
|
|
#include "py_helper.h"
|
|
#include "py_image.h"
|
|
#include "omv_gpio.h"
|
|
#include "omv_spi.h"
|
|
|
|
#define TV_WIDTH 352
|
|
#define TV_HEIGHT 240
|
|
#define TV_REFRESH 60
|
|
|
|
#if ((TV_WIDTH) % 2)
|
|
#error "TV_WIDTH not even"
|
|
#endif
|
|
|
|
#if ((TV_HEIGHT) % 2)
|
|
#error "TV_HEIGHT not even"
|
|
#endif
|
|
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
/////////////////////////////////////////////////////////////
|
|
// http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=14&t=1801
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
// Crystal frequency in MHZ (float, observe accuracy)
|
|
#define XTAL_MHZ 3.579545
|
|
|
|
// Line length in microseconds (float, observe accuracy)
|
|
#define LINE_LENGTH_US 63.556
|
|
|
|
#define FIXED_VCLK_CYCLES 10
|
|
#define FIXED_CSCLK_CYCLES ((FIXED_VCLK_CYCLES) / 8.0)
|
|
|
|
// Normal visible picture line sync length is 4.7 us
|
|
#define SYNC_US 4.7
|
|
#define SYNC ((uint16_t) (((SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5))
|
|
|
|
// Color burst starts at 5.3 us
|
|
#define BURST_US 5.3
|
|
#define BURST ((uint16_t) (((BURST_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5))
|
|
|
|
// Color burst duration is 2.5 us
|
|
#define BURST_DUR_US 2.5
|
|
#define BURST_DUR ((uint16_t) (((BURST_DUR_US) *(XTAL_MHZ)) + 0.5))
|
|
|
|
// Black video starts at 9.4 us
|
|
#define BLACK_US 9.4
|
|
#define BLACK ((uint16_t) (((BLACK_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5))
|
|
|
|
// Black video duration is 52.656 us
|
|
#define BLACK_DUR_US 52.656
|
|
#define BLACK_DUR ((uint16_t) (((BLACK_DUR_US) *(XTAL_MHZ)) + 0.5))
|
|
|
|
// Define NTSC video timing constants
|
|
// NTSC short sync duration is 2.3 us
|
|
#define SHORT_SYNC_US 2.3
|
|
|
|
// For the start of the line, the first 10 extra PLLCLK sync (0) cycles are subtracted.
|
|
#define SHORTSYNC ((uint16_t) (((SHORT_SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5))
|
|
|
|
// For the middle of the line the whole duration of sync pulse is used.
|
|
#define SHORTSYNCM ((uint16_t) (((SHORT_SYNC_US) *(XTAL_MHZ)) + 0.5))
|
|
|
|
// NTSC long sync duration is 27.078 us
|
|
#define LONG_SYNC_US 27.078
|
|
#define LONGSYNC ((uint16_t) (((LONG_SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5))
|
|
#define LONGSYNCM ((uint16_t) (((LONG_SYNC_US) *(XTAL_MHZ)) + 0.5))
|
|
|
|
// Number of lines used after the VSYNC but before visible area.
|
|
#define VSYNC_LINES 9
|
|
#define FRONT_PORCH_LINES 13
|
|
|
|
// Definitions for picture lines
|
|
// On which line the picture area begins, the Y direction.
|
|
#define STARTLINE ((VSYNC_LINES) + (FRONT_PORCH_LINES))
|
|
|
|
// Frame length in lines (visible lines + nonvisible lines)
|
|
// Amount has to be odd for NTSC and RGB colors
|
|
#define TOTAL_LINES ((STARTLINE) + (TV_HEIGHT) +1)
|
|
#if ((TOTAL_LINES) != 263)
|
|
#error "Progressive NTSC must have 263 lines!"
|
|
#endif
|
|
|
|
// Width, in PLL clocks, of each pixel.
|
|
#define PLLCLKS_PER_PIXEL 4
|
|
|
|
// The first pixel of the picture area, the X direction.
|
|
#define STARTPIX ((BLACK) +7)
|
|
|
|
// The last pixel of the picture area.
|
|
#define ENDPIX ((uint16_t) ((STARTPIX) + (((PLLCLKS_PER_PIXEL) *(TV_WIDTH)) / 8)))
|
|
|
|
// Reserve memory for this number of different prototype lines
|
|
// (prototype lines are used for sync timing, porch and border area)
|
|
#define PROTOLINES 3
|
|
|
|
// PLL frequency
|
|
#define PLL_MHZ ((XTAL_MHZ) * 8)
|
|
|
|
// 10 first pllclks, which are not in the counters are decremented here
|
|
#define PLLCLKS_PER_LINE ((uint16_t) (((LINE_LENGTH_US) *(PLL_MHZ)) - (FIXED_VCLK_CYCLES)))
|
|
|
|
// 10 first pllclks, which are not in the counters are decremented here
|
|
#define COLORCLKS_PER_LINE ((uint16_t) ((((((LINE_LENGTH_US) *(PLL_MHZ)) / 1) + 7) / 8) - (FIXED_CSCLK_CYCLES)))
|
|
#define COLORCLKS_LINE_HALF ((uint16_t) ((((((LINE_LENGTH_US) *(PLL_MHZ)) / 2) + 7) / 8) - (FIXED_CSCLK_CYCLES)))
|
|
|
|
#define PROTO_AREA_WORDS ((COLORCLKS_PER_LINE) *(PROTOLINES))
|
|
#define INDEX_START_LONGWORDS (((PROTO_AREA_WORDS) +1) / 2)
|
|
#define INDEX_START_BYTES ((INDEX_START_LONGWORDS) * 4)
|
|
|
|
// Protoline 0 starts always at address 0
|
|
#define PROTOLINE_BYTE_ADDRESS(n) ((COLORCLKS_PER_LINE) * 2 * (n))
|
|
#define PROTOLINE_WORD_ADDRESS(n) ((COLORCLKS_PER_LINE) * 1 * (n))
|
|
|
|
// Calculate picture lengths in pixels and bytes, coordinate areas for picture area
|
|
#define PICBITS 12
|
|
#define PICLINE_LENGTH_BYTES (((TV_WIDTH) *(PICBITS)) / 8)
|
|
|
|
#define LINE_INDEX_BYTE_SIZE 3
|
|
|
|
// Picture area memory start point
|
|
#define PICLINE_START ((INDEX_START_BYTES) + ((TOTAL_LINES) *(LINE_INDEX_BYTE_SIZE)))
|
|
|
|
// Picture area line start addresses
|
|
#define PICLINE_BYTE_ADDRESS(n) ((PICLINE_START) + ((PICLINE_LENGTH_BYTES) *(n)))
|
|
|
|
// Pattern generator microcode
|
|
// ---------------------------
|
|
// Bits 7:6 a=00|b=01|y=10|-=11
|
|
// Bits 5:3 n pick bits 1..8
|
|
// bits 2:0 shift 0..6
|
|
#define PICK_A (0 << 6)
|
|
#define PICK_B (1 << 6)
|
|
#define PICK_Y (2 << 6)
|
|
#define PICK_NOTHING (3 << 6)
|
|
#define PICK_BITS(a) (((a) - 1) << 3)
|
|
#define SHIFT_BITS(a) (a)
|
|
|
|
// 16 bits per pixel, U4 V4 Y8
|
|
// PICK_B is U
|
|
#define OP1 (PICK_B + PICK_BITS(4) + SHIFT_BITS(4))
|
|
// PICK_A is V
|
|
#define OP2 (PICK_A + PICK_BITS(4) + SHIFT_BITS(4))
|
|
#define OP3 (PICK_Y + PICK_BITS(8) + SHIFT_BITS(6))
|
|
#define OP4 (PICK_NOTHING + SHIFT_BITS(2))
|
|
|
|
// General VS23 commands
|
|
#define WRITE_STATUS 0x01
|
|
#define WRITE_SRAM 0x02
|
|
#define WRITE_GPIO 0x82
|
|
#define READ_GPIO 0x84
|
|
#define WRITE_MULTIIC 0xb8
|
|
#define WRITE_BLOCKMVC1 0x34
|
|
|
|
// Bit definitions
|
|
#define VDCTRL1 0x2B
|
|
#define VDCTRL1_UVSKIP (1 << 0)
|
|
#define VDCTRL1_PLL_ENABLE (1 << 12)
|
|
#define VDCTRL2 0x2D
|
|
#define VDCTRL2_LINECOUNT (1 << 0)
|
|
#define VDCTRL2_PIXEL_WIDTH (1 << 10)
|
|
#define VDCTRL2_ENABLE_VIDEO (1 << 15)
|
|
#define BLOCKMVC1_PYF (1 << 4)
|
|
|
|
// VS23 video commands
|
|
#define PROGRAM 0x30
|
|
#define PICSTART 0x28
|
|
#define PICEND 0x29
|
|
#define LINELEN 0x2a
|
|
#define INDEXSTART 0x2c
|
|
|
|
// Sync, blank, burst and white level definitions, here are several options
|
|
// These are for proto lines and so format is VVVVUUUUYYYYYYYY
|
|
|
|
// Sync is always 0
|
|
#define SYNC_LEVEL 0x0000
|
|
|
|
// 285 mV to 75 ohm load
|
|
#define BLANK_LEVEL 0x0066
|
|
|
|
// 285 mV burst
|
|
#define BURST_LEVEL 0x0d66
|
|
|
|
#define SPI_RAM_SIZE (128 * 1024)
|
|
|
|
// COLORCLKS_PER_LINE can't be used in pre-processor logic.
|
|
#if ((((((227 * (PROTOLINES)) + 1) / 2) * 4) + ((TOTAL_LINES) *(LINE_INDEX_BYTE_SIZE)) + \
|
|
((PICLINE_LENGTH_BYTES) *(TV_HEIGHT))) > (SPI_RAM_SIZE))
|
|
#error "TV_WIDTH * TV_HEIGHT is too big!"
|
|
#endif
|
|
|
|
#define TV_BAUDRATE (TV_WIDTH * TV_HEIGHT * TV_REFRESH * PICBITS)
|
|
|
|
#if OMV_SPI_DISPLAY_TRIPLE_BUFFER
|
|
#define TV_TRIPLE_BUFFER_DEFAULT (true)
|
|
#else
|
|
#define TV_TRIPLE_BUFFER_DEFAULT (false)
|
|
#endif
|
|
|
|
static omv_spi_t spi_bus = {};
|
|
|
|
static void SpiTransmitReceivePacket(uint8_t *txdata, uint8_t *rxdata, uint16_t size, bool end) {
|
|
omv_spi_transfer_t spi_xfer = {
|
|
.txbuf = txdata,
|
|
.rxbuf = rxdata,
|
|
.size = size,
|
|
.timeout = OMV_SPI_MAX_TIMEOUT,
|
|
.flags = OMV_SPI_XFER_BLOCKING
|
|
};
|
|
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0);
|
|
omv_spi_transfer_start(&spi_bus, &spi_xfer);
|
|
|
|
if (end) {
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1);
|
|
}
|
|
}
|
|
|
|
static void SpiRamWriteByteRegister(int opcode, int data) {
|
|
uint8_t packet[2] = {opcode, data};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static int SpiRamReadByteRegister(int opcode) {
|
|
uint8_t packet[2] = {opcode, 0};
|
|
SpiTransmitReceivePacket(packet, packet, sizeof(packet), true);
|
|
return packet[1];
|
|
}
|
|
|
|
static void SpiRamWriteWordRegister(int opcode, int data) {
|
|
uint8_t packet[3] = {opcode, data >> 8, data};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static void SpiClearRam() {
|
|
uint8_t packet[4] = {WRITE_SRAM, 0, 0, 0};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), false);
|
|
packet[0] = 0;
|
|
|
|
for (int i = 0; i < (SPI_RAM_SIZE / sizeof(packet)); i++) {
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), (i + 1) == (SPI_RAM_SIZE / sizeof(packet)));
|
|
}
|
|
}
|
|
|
|
static void SpiRamWriteProgram(int data0, int data1, int data2, int data3) {
|
|
uint8_t packet[5] = {PROGRAM, data3, data2, data1, data0};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static void SpiRamWriteLowPassFilter(int data) {
|
|
uint8_t packet[6] = {WRITE_BLOCKMVC1, 0, 0, 0, 0, data};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static void SpiRamWriteWord(int w_address, int data) {
|
|
int address = w_address * sizeof(uint16_t);
|
|
uint8_t packet[6] = {WRITE_SRAM, address >> 16, address >> 8, address, data >> 8, data};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static void SpiRamWriteVSyncProtoLine(int line, int length_1, int length_2) {
|
|
int w0 = PROTOLINE_WORD_ADDRESS(line);
|
|
for (int i = 0; i < COLORCLKS_PER_LINE; i++) {
|
|
SpiRamWriteWord(w0++, BLANK_LEVEL);
|
|
}
|
|
|
|
int w1 = PROTOLINE_WORD_ADDRESS(line);
|
|
for (int i = 0; i < length_1; i++) {
|
|
SpiRamWriteWord(w1++, SYNC_LEVEL);
|
|
}
|
|
|
|
int w2 = PROTOLINE_WORD_ADDRESS(line) + COLORCLKS_LINE_HALF;
|
|
for (int i = 0; i < length_2; i++) {
|
|
SpiRamWriteWord(w2++, SYNC_LEVEL);
|
|
}
|
|
}
|
|
|
|
static void SpiRamWriteLine(int line, int index) {
|
|
int address = INDEX_START_BYTES + (line * LINE_INDEX_BYTE_SIZE);
|
|
int data = index << 7;
|
|
uint8_t packet[7] = {WRITE_SRAM, address >> 16, address >> 8, address, data, data >> 8, data >> 16};
|
|
SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true);
|
|
}
|
|
|
|
static void SpiRamVideoInit() {
|
|
// Select the first VS23 for following commands in case there
|
|
// are several VS23 ICs connected to same SPI bus.
|
|
SpiRamWriteByteRegister(WRITE_MULTIIC, 0xe);
|
|
|
|
// Set SPI memory address autoincrement
|
|
SpiRamWriteByteRegister(WRITE_STATUS, 0x40);
|
|
|
|
// Reset the video display controller
|
|
SpiRamWriteWordRegister(VDCTRL1, 0);
|
|
SpiRamWriteWordRegister(VDCTRL2, 0);
|
|
|
|
// Write picture start and end
|
|
SpiRamWriteWordRegister(PICSTART, (STARTPIX - 1));
|
|
SpiRamWriteWordRegister(PICEND, (ENDPIX - 1));
|
|
|
|
// Enable PLL clock
|
|
SpiRamWriteWordRegister(VDCTRL1, VDCTRL1_PLL_ENABLE | VDCTRL1_UVSKIP);
|
|
|
|
// Clear the video memory
|
|
SpiClearRam();
|
|
|
|
// Set length of one complete line (unit: PLL clocks)
|
|
SpiRamWriteWordRegister(LINELEN, PLLCLKS_PER_LINE);
|
|
|
|
// Set microcode program for picture lines
|
|
SpiRamWriteProgram(OP1, OP2, OP3, OP4);
|
|
|
|
// Define where Line Indexes are stored in memory
|
|
SpiRamWriteWordRegister(INDEXSTART, INDEX_START_LONGWORDS);
|
|
|
|
// At this time, the chip would continuously output the proto line 0.
|
|
// This protoline will become our most "normal" horizontal line.
|
|
// For TV-Out, fill the line with black level,
|
|
// and insert a few pixels of sync level (0) and color burst to the beginning.
|
|
// Note that the chip hardware adds black level to all nonproto areas so
|
|
// protolines and normal picture have different meaning for the same Y value.
|
|
// In protolines, Y=0 is at sync level and in normal picture Y=0 is at black level (offset +102).
|
|
|
|
// In protolines, each pixel is 8 PLLCLKs, which in TV-out modes means one color
|
|
// subcarrier cycle. Each pixel has 16 bits (one word): VVVVUUUUYYYYYYYY.
|
|
|
|
SpiRamWriteVSyncProtoLine(0, SYNC, 0);
|
|
|
|
int w = PROTOLINE_WORD_ADDRESS(0) + BURST;
|
|
for (int i = 0; i < BURST_DUR; i++) {
|
|
SpiRamWriteWord(w++, BURST_LEVEL);
|
|
}
|
|
|
|
// short_low + long_high + short_low + long_high
|
|
SpiRamWriteVSyncProtoLine(1, SHORTSYNC, SHORTSYNCM);
|
|
|
|
// long_low + short_high + long_low + short_high
|
|
SpiRamWriteVSyncProtoLine(2, LONGSYNC, LONGSYNCM);
|
|
|
|
for (int i = 0; i <= 2; i++) {
|
|
SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(1)); // short_low + long_high + short_low + long_high
|
|
}
|
|
|
|
for (int i = 3; i <= 5; i++) {
|
|
SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(2)); // long_low + short_high + long_low + short_high
|
|
}
|
|
|
|
for (int i = 6; i <= 8; i++) {
|
|
SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(1)); // short_low + long_high + short_low + long_high
|
|
}
|
|
|
|
// Set pic line indexes to point to protoline 0 and their individual picture line.
|
|
for (int i = 0; i < TV_HEIGHT; i++) {
|
|
SpiRamWriteLine(STARTLINE + i, PICLINE_BYTE_ADDRESS(i));
|
|
}
|
|
|
|
// Set number of lines, length of pixel and enable video generation
|
|
SpiRamWriteWordRegister(VDCTRL2, (VDCTRL2_LINECOUNT * (TOTAL_LINES - 1))
|
|
| (VDCTRL2_PIXEL_WIDTH * (PLLCLKS_PER_PIXEL - 1))
|
|
| (VDCTRL2_ENABLE_VIDEO));
|
|
|
|
// Enable the low-pass Y filter.
|
|
SpiRamWriteLowPassFilter(BLOCKMVC1_PYF);
|
|
}
|
|
#endif
|
|
|
|
// TV lines are converted from 16-bit RGB565 to 12-bit YUV.
|
|
#define TV_WIDTH_RGB565 ((TV_WIDTH) * 2) // bytes
|
|
|
|
#if ((PICLINE_LENGTH_BYTES) > (TV_WIDTH_RGB565))
|
|
#error "PICLINE_LENGTH_BYTES > TV_WIDTH_RGB565"
|
|
#endif
|
|
|
|
#define FRAMEBUFFER_COUNT 3
|
|
static int framebuffer_head = 0;
|
|
static volatile int framebuffer_tail = 0;
|
|
static uint16_t *framebuffers[FRAMEBUFFER_COUNT] = {};
|
|
|
|
typedef enum tv_type {
|
|
TV_NONE,
|
|
TV_SHIELD,
|
|
} tv_type_t;
|
|
|
|
static tv_type_t tv_type = TV_NONE;
|
|
static bool tv_triple_buffer = false;
|
|
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
static volatile enum {
|
|
SPI_TX_CB_IDLE,
|
|
SPI_TX_CB_MEMORY_WRITE_CMD,
|
|
SPI_TX_CB_MEMORY_WRITE
|
|
}
|
|
spi_tx_cb_state = SPI_TX_CB_IDLE;
|
|
|
|
static void spi_config_deinit() {
|
|
if (tv_triple_buffer) {
|
|
omv_spi_transfer_abort(&spi_bus);
|
|
spi_tx_cb_state = SPI_TX_CB_IDLE;
|
|
fb_alloc_free_till_mark_past_mark_permanent();
|
|
}
|
|
|
|
omv_spi_deinit(&spi_bus);
|
|
}
|
|
|
|
static void spi_config_init(bool triple_buffer) {
|
|
omv_spi_config_t spi_config;
|
|
omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER);
|
|
|
|
spi_config.baudrate = TV_BAUDRATE;
|
|
spi_config.nss_enable = false;
|
|
spi_config.dma_flags = triple_buffer ? OMV_SPI_DMA_NORMAL : 0;
|
|
omv_spi_init(&spi_bus, &spi_config);
|
|
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1);
|
|
|
|
SpiRamVideoInit();
|
|
|
|
// Set default channel.
|
|
SpiRamWriteByteRegister(WRITE_GPIO, 0x77);
|
|
|
|
if (triple_buffer) {
|
|
fb_alloc_mark();
|
|
|
|
framebuffer_head = 0;
|
|
framebuffer_tail = 0;
|
|
|
|
for (int i = 0; i < FRAMEBUFFER_COUNT; i++) {
|
|
framebuffers[i] = (uint16_t *) fb_alloc0(TV_WIDTH_RGB565 * TV_HEIGHT, FB_ALLOC_CACHE_ALIGN);
|
|
}
|
|
|
|
fb_alloc_mark_permanent();
|
|
}
|
|
}
|
|
|
|
static const uint8_t write_sram[] = {
|
|
// Cannot be allocated on the stack.
|
|
WRITE_SRAM,
|
|
(uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 16),
|
|
(uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 8),
|
|
(uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 0)
|
|
};
|
|
|
|
static void spi_tv_callback(omv_spi_t *spi, void *userdata, void *buf) {
|
|
if (tv_type == TV_SHIELD) {
|
|
static uint8_t *spi_tx_cb_state_memory_write_addr = NULL;
|
|
static size_t spi_tx_cb_state_memory_write_count = 0;
|
|
|
|
switch (spi_tx_cb_state) {
|
|
case SPI_TX_CB_MEMORY_WRITE_CMD: {
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1);
|
|
spi_tx_cb_state = SPI_TX_CB_MEMORY_WRITE;
|
|
spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_head];
|
|
spi_tx_cb_state_memory_write_count = PICLINE_LENGTH_BYTES * TV_HEIGHT;
|
|
framebuffer_tail = framebuffer_head;
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0);
|
|
// When starting the interrupt chain the first transfer is not executed
|
|
// in interrupt context. So, disable interrupts for the first transfer so
|
|
// that it completes first and unlocks the SPI bus before allowing the interrupt
|
|
// it causes to trigger starting the interrupt chain.
|
|
omv_spi_transfer_t spi_xfer = {
|
|
.txbuf = (uint8_t *) write_sram,
|
|
.size = sizeof(write_sram),
|
|
.flags = OMV_SPI_XFER_NONBLOCK,
|
|
.callback = spi_tv_callback,
|
|
};
|
|
uint32_t irq_state = MICROPY_BEGIN_ATOMIC_SECTION();
|
|
omv_spi_transfer_start(&spi_bus, &spi_xfer);
|
|
MICROPY_END_ATOMIC_SECTION(irq_state);
|
|
break;
|
|
}
|
|
case SPI_TX_CB_MEMORY_WRITE: {
|
|
uint8_t *addr = spi_tx_cb_state_memory_write_addr;
|
|
size_t count = IM_MIN(spi_tx_cb_state_memory_write_count, OMV_SPI_MAX_8BIT_XFER);
|
|
spi_tx_cb_state = (spi_tx_cb_state_memory_write_count > OMV_SPI_MAX_8BIT_XFER)
|
|
? SPI_TX_CB_MEMORY_WRITE
|
|
: SPI_TX_CB_MEMORY_WRITE_CMD;
|
|
spi_tx_cb_state_memory_write_addr += count;
|
|
spi_tx_cb_state_memory_write_count -= count;
|
|
omv_spi_transfer_t spi_xfer = {
|
|
.txbuf = addr,
|
|
.size = count,
|
|
.flags = OMV_SPI_XFER_DMA,
|
|
.callback = spi_tv_callback,
|
|
};
|
|
omv_spi_transfer_start(&spi_bus, &spi_xfer);
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert a 8-bit Grayscale line of pixels to 12-bit YUV422 with padding (line is 16-bit per pixel).
|
|
static void spi_tv_draw_image_cb_convert_grayscale(uint8_t *row_pointer_i, uint8_t *row_pointer_o) {
|
|
for (int i = TV_WIDTH - 2, j = ((TV_WIDTH * 3) / 2) - 3; i >= 0; i -= 2, j -= 3) {
|
|
int y0 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_pointer_i, i);
|
|
int y1 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_pointer_i, i + 1);
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j, 0);
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 1, y0);
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 2, y1);
|
|
}
|
|
}
|
|
|
|
// Convert a 16-bit RGB565 line of pixels to 12-bit YUV422 with padding (line is 16-bit per pixel).
|
|
static void spi_tv_draw_image_cb_convert_rgb565(uint16_t *row_pointer_i, uint8_t *row_pointer_o) {
|
|
for (int i = 0, j = 0; i < TV_WIDTH; i += 2, j += 3) {
|
|
#if defined(ARM_MATH_DSP)
|
|
|
|
int pixels = *((uint32_t *) (row_pointer_i + i));
|
|
int r_pixels = ((pixels >> 8) & 0xf800f8) | ((pixels >> 13) & 0x70007);
|
|
int g_pixels = ((pixels >> 3) & 0xfc00fc) | ((pixels >> 9) & 0x30003);
|
|
int b_pixels = ((pixels << 3) & 0xf800f8) | ((pixels >> 2) & 0x70007);
|
|
|
|
int y = ((r_pixels * 38) + (g_pixels * 75) + (b_pixels * 15)) >> 7;
|
|
int u = __SSUB16(b_pixels * 64, (r_pixels * 21) + (g_pixels * 43));
|
|
int v = __SSUB16(r_pixels * 64, (g_pixels * 54) + (b_pixels * 10));
|
|
|
|
int y0 = __UXTB_RORn(y, 0), y1 = __UXTB_RORn(y, 16);
|
|
|
|
int u_avg = __SMUAD(u, 0x00010001) >> 7;
|
|
int v_avg = __SMUAD(v, 0x00010001) >> 7;
|
|
|
|
#else
|
|
|
|
int pixel0 = IMAGE_GET_RGB565_PIXEL_FAST(row_pointer_i, i);
|
|
int r0 = COLOR_RGB565_TO_R8(pixel0);
|
|
int g0 = COLOR_RGB565_TO_G8(pixel0);
|
|
int b0 = COLOR_RGB565_TO_B8(pixel0);
|
|
int y0 = COLOR_RGB888_TO_Y(r0, g0, b0);
|
|
int u0 = COLOR_RGB888_TO_U(r0, g0, b0);
|
|
int v0 = COLOR_RGB888_TO_V(r0, g0, b0);
|
|
|
|
int pixel1 = IMAGE_GET_RGB565_PIXEL_FAST(row_pointer_i, i + 1);
|
|
int r1 = COLOR_RGB565_TO_R8(pixel1);
|
|
int g1 = COLOR_RGB565_TO_G8(pixel1);
|
|
int b1 = COLOR_RGB565_TO_B8(pixel1);
|
|
int y1 = COLOR_RGB888_TO_Y(r1, g1, b1);
|
|
int u1 = COLOR_RGB888_TO_U(r1, g1, b1);
|
|
int v1 = COLOR_RGB888_TO_V(r1, g1, b1);
|
|
|
|
int u_avg = u0 + u1;
|
|
int v_avg = v0 + v1;
|
|
|
|
#endif
|
|
|
|
int uv = ((u_avg >> 1) & 0xf0) | (((-v_avg) >> 5) & 0xf);
|
|
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j, uv);
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 1, y0);
|
|
IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 2, y1);
|
|
}
|
|
}
|
|
|
|
static void spi_tv_draw_image_cb_grayscale(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) {
|
|
memset(((uint8_t *) data->dst_row_override) + x_end, 0, TV_WIDTH - x_end); // clear trailing bytes.
|
|
spi_tv_draw_image_cb_convert_grayscale((uint8_t *) data->dst_row_override, (uint8_t *) data->dst_row_override);
|
|
SpiTransmitReceivePacket(data->dst_row_override, NULL, PICLINE_LENGTH_BYTES, false);
|
|
}
|
|
|
|
static void spi_tv_draw_image_cb_rgb565(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) {
|
|
memset(data->dst_row_override, 0, x_start * sizeof(uint16_t)); // clear leading bytes.
|
|
spi_tv_draw_image_cb_convert_rgb565((uint16_t *) data->dst_row_override, (uint8_t *) data->dst_row_override);
|
|
SpiTransmitReceivePacket(data->dst_row_override, NULL, PICLINE_LENGTH_BYTES, false);
|
|
}
|
|
|
|
static void spi_tv_display(image_t *src_img, int dst_x_start, int dst_y_start, float x_scale, float y_scale,
|
|
rectangle_t *roi, int rgb_channel, int alpha,
|
|
const uint16_t *color_palette, const uint8_t *alpha_palette,
|
|
image_hint_t hint) {
|
|
bool rgb565 = ((rgb_channel == -1) && src_img->is_color) || color_palette;
|
|
imlib_draw_row_callback_t cb = rgb565 ? spi_tv_draw_image_cb_rgb565 : spi_tv_draw_image_cb_grayscale;
|
|
|
|
image_t dst_img;
|
|
dst_img.w = TV_WIDTH;
|
|
dst_img.h = TV_HEIGHT;
|
|
dst_img.pixfmt = rgb565 ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE;
|
|
|
|
point_t p0, p1;
|
|
imlib_draw_image_get_bounds(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale,
|
|
roi, alpha, alpha_palette, hint, &p0, &p1);
|
|
bool black = p0.x == -1;
|
|
|
|
if (!tv_triple_buffer) {
|
|
dst_img.data = fb_alloc0(TV_WIDTH_RGB565, FB_ALLOC_NO_HINT);
|
|
|
|
SpiTransmitReceivePacket((uint8_t *) write_sram, NULL, sizeof(write_sram), false);
|
|
|
|
if (black) {
|
|
// zero the whole image
|
|
for (int i = 0; i < TV_HEIGHT; i++) {
|
|
SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false);
|
|
}
|
|
} else {
|
|
// Zero the top rows
|
|
for (int i = 0; i < p0.y; i++) {
|
|
SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false);
|
|
}
|
|
|
|
// Transmits left/right parts already zeroed...
|
|
imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi,
|
|
rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND,
|
|
NULL, cb, NULL, dst_img.data);
|
|
|
|
// Zero the bottom rows
|
|
if (p1.y < TV_HEIGHT) {
|
|
memset(dst_img.data, 0, TV_WIDTH_RGB565);
|
|
}
|
|
|
|
for (int i = p1.y; i < TV_HEIGHT; i++) {
|
|
SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false);
|
|
}
|
|
}
|
|
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1);
|
|
fb_free();
|
|
} else {
|
|
// For triple buffering we are never drawing where head or tail (which may instantly update to
|
|
// to be equal to head) is.
|
|
int new_framebuffer_head = (framebuffer_head + 1) % FRAMEBUFFER_COUNT;
|
|
if (new_framebuffer_head == framebuffer_tail) {
|
|
new_framebuffer_head = (new_framebuffer_head + 1) % FRAMEBUFFER_COUNT;
|
|
}
|
|
|
|
dst_img.data = (uint8_t *) framebuffers[new_framebuffer_head];
|
|
|
|
if (rgb565) {
|
|
if (black) {
|
|
// zero the whole image
|
|
memset(dst_img.data, 0, TV_WIDTH * TV_HEIGHT * sizeof(uint16_t));
|
|
} else {
|
|
// Zero the top rows
|
|
if (p0.y) {
|
|
memset(dst_img.data, 0, TV_WIDTH * p0.y * sizeof(uint16_t));
|
|
}
|
|
|
|
if (p0.x) {
|
|
for (int i = p0.y; i < p1.y; i++) {
|
|
// Zero left
|
|
memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), 0, p0.x * sizeof(uint16_t));
|
|
}
|
|
}
|
|
|
|
imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi,
|
|
rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
if (TV_WIDTH - p1.x) {
|
|
for (int i = p0.y; i < p1.y; i++) {
|
|
// Zero right
|
|
memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i) + p1.x, 0,
|
|
(TV_WIDTH - p1.x) * sizeof(uint16_t));
|
|
}
|
|
}
|
|
|
|
// Zero the bottom rows
|
|
if (TV_HEIGHT - p1.y) {
|
|
memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, p1.y), 0,
|
|
TV_WIDTH * (TV_HEIGHT - p1.y) * sizeof(uint16_t));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < TV_HEIGHT; i++) {
|
|
// Convert the image.
|
|
spi_tv_draw_image_cb_convert_rgb565(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i),
|
|
dst_img.data + (PICLINE_LENGTH_BYTES * i));
|
|
}
|
|
} else {
|
|
if (black) {
|
|
// zero the whole image
|
|
memset(dst_img.data, 0, TV_WIDTH * TV_HEIGHT * sizeof(uint8_t));
|
|
} else {
|
|
// Zero the top rows
|
|
if (p0.y) {
|
|
memset(dst_img.data, 0, TV_WIDTH * p0.y * sizeof(uint8_t));
|
|
}
|
|
|
|
if (p0.x) {
|
|
for (int i = p0.y; i < p1.y; i++) {
|
|
// Zero left
|
|
memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i), 0, p0.x * sizeof(uint8_t));
|
|
}
|
|
}
|
|
|
|
imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi,
|
|
rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND,
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
if (TV_WIDTH - p1.x) {
|
|
for (int i = p0.y; i < p1.y; i++) {
|
|
// Zero right
|
|
memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i) + p1.x, 0,
|
|
(TV_WIDTH - p1.x) * sizeof(uint8_t));
|
|
}
|
|
}
|
|
|
|
// Zero the bottom rows
|
|
if (TV_HEIGHT - p1.y) {
|
|
memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, p1.y), 0,
|
|
TV_WIDTH * (TV_HEIGHT - p1.y) * sizeof(uint8_t));
|
|
}
|
|
}
|
|
|
|
for (int i = TV_HEIGHT - 1; i >= 0; i--) {
|
|
// Convert the image.
|
|
spi_tv_draw_image_cb_convert_grayscale(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i),
|
|
dst_img.data + (PICLINE_LENGTH_BYTES * i));
|
|
}
|
|
}
|
|
|
|
#ifdef __DCACHE_PRESENT
|
|
// Flush data for DMA
|
|
SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img));
|
|
#endif
|
|
|
|
// Update head which means a new image is ready.
|
|
framebuffer_head = new_framebuffer_head;
|
|
|
|
// Kick off an update of the display.
|
|
if (spi_tx_cb_state == SPI_TX_CB_IDLE) {
|
|
spi_tx_cb_state = SPI_TX_CB_MEMORY_WRITE_CMD;
|
|
spi_tv_callback(&spi_bus, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static mp_obj_t py_tv_deinit() {
|
|
switch (tv_type) {
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
case TV_SHIELD: {
|
|
spi_config_deinit();
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tv_type = TV_NONE;
|
|
tv_triple_buffer = false;
|
|
|
|
return mp_const_none;
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_deinit_obj, py_tv_deinit);
|
|
|
|
static mp_obj_t py_tv_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
|
enum { ARG_type, ARG_triple_buffer };
|
|
static const mp_arg_t allowed_args[] = {
|
|
{ MP_QSTR_type, MP_ARG_INT, {.u_int = TV_SHIELD } },
|
|
{ MP_QSTR_triple_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = TV_TRIPLE_BUFFER_DEFAULT } },
|
|
};
|
|
|
|
// Parse args.
|
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
|
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
|
|
|
py_tv_deinit();
|
|
tv_type = args[ARG_type].u_int;
|
|
tv_triple_buffer = args[ARG_triple_buffer].u_bool;
|
|
|
|
switch (tv_type) {
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
case TV_SHIELD:
|
|
spi_config_init(tv_triple_buffer);
|
|
break;
|
|
#endif
|
|
default:
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to detect a supported TV controller."));
|
|
}
|
|
|
|
return mp_const_none;
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_init_obj, 0, py_tv_init);
|
|
|
|
static mp_obj_t py_tv_width() {
|
|
if (tv_type != TV_NONE) {
|
|
return mp_obj_new_int(TV_WIDTH);
|
|
}
|
|
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_width_obj, py_tv_width);
|
|
|
|
static mp_obj_t py_tv_height() {
|
|
if (tv_type != TV_NONE) {
|
|
return mp_obj_new_int(TV_HEIGHT);
|
|
}
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_height_obj, py_tv_height);
|
|
|
|
static mp_obj_t py_tv_type() {
|
|
if (tv_type != TV_NONE) {
|
|
return mp_obj_new_int(tv_type);
|
|
}
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_type_obj, py_tv_type);
|
|
|
|
static mp_obj_t py_tv_triple_buffer() {
|
|
if (tv_type != TV_NONE) {
|
|
return mp_obj_new_int(tv_triple_buffer);
|
|
}
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_triple_buffer_obj, py_tv_triple_buffer);
|
|
|
|
static mp_obj_t py_tv_refresh() {
|
|
if (tv_type != TV_NONE) {
|
|
return mp_obj_new_int(TV_REFRESH);
|
|
}
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_refresh_obj, py_tv_refresh);
|
|
|
|
static mp_obj_t py_tv_channel(size_t n_args, const mp_obj_t *args) {
|
|
if (tv_type == TV_NONE) {
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
if (tv_triple_buffer) {
|
|
omv_spi_transfer_abort(&spi_bus);
|
|
spi_tx_cb_state = SPI_TX_CB_IDLE;
|
|
omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1);
|
|
}
|
|
|
|
if (n_args) {
|
|
int channel = mp_obj_get_int(*args);
|
|
|
|
if ((channel < 1) || (8 < channel)) {
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Channel ranges between 1 and 8"));
|
|
}
|
|
|
|
SpiRamWriteByteRegister(WRITE_GPIO, 0x70 | (channel - 1));
|
|
return mp_const_none;
|
|
} else {
|
|
#ifdef OMV_SPI_DISPLAY_RX_CLK_DIV
|
|
omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_DISPLAY_RX_CLK_DIV);
|
|
#endif
|
|
int channel = SpiRamReadByteRegister(READ_GPIO);
|
|
#ifdef OMV_SPI_DISPLAY_RX_CLK_DIV
|
|
omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE);
|
|
#endif
|
|
return mp_obj_new_int((channel & 0x7) + 1);
|
|
}
|
|
#endif
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_tv_channel_obj, 0, 1, py_tv_channel);
|
|
|
|
static mp_obj_t py_tv_display(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
|
enum {
|
|
ARG_x, ARG_y, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, ARG_alpha,
|
|
ARG_color_palette, ARG_alpha_palette, ARG_hint
|
|
};
|
|
static const mp_arg_t allowed_args[] = {
|
|
{ MP_QSTR_x, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } },
|
|
{ MP_QSTR_y, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } },
|
|
{ MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } },
|
|
{ MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 255 } },
|
|
{ MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } },
|
|
};
|
|
|
|
// Parse args.
|
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
|
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
|
|
|
image_t *image = py_helper_arg_to_image(pos_args[0], 0);
|
|
rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image);
|
|
|
|
if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) {
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2"));
|
|
}
|
|
|
|
if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 255) {
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 255"));
|
|
}
|
|
|
|
float x_scale = 1.0f;
|
|
float y_scale = 1.0f;
|
|
py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale);
|
|
|
|
const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565);
|
|
const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE);
|
|
|
|
switch (tv_type) {
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
case TV_SHIELD: {
|
|
fb_alloc_mark();
|
|
spi_tv_display(image, args[ARG_x].u_int, args[ARG_y].u_int, x_scale, y_scale, &roi,
|
|
args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette,
|
|
args[ARG_hint].u_int);
|
|
fb_alloc_free_till_mark();
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
|
|
return mp_const_none;
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_display_obj, 1, py_tv_display);
|
|
|
|
static mp_obj_t py_tv_clear() {
|
|
switch (tv_type) {
|
|
#ifdef OMV_SPI_DISPLAY_CONTROLLER
|
|
case TV_SHIELD: {
|
|
fb_alloc_mark();
|
|
spi_tv_display(NULL, 0, 0, 1.f, 1.f, NULL,
|
|
0, 0, NULL, NULL, 0);
|
|
fb_alloc_free_till_mark();
|
|
break;
|
|
}
|
|
#endif
|
|
default: {
|
|
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized"));
|
|
}
|
|
}
|
|
|
|
return mp_const_none;
|
|
}
|
|
static MP_DEFINE_CONST_FUN_OBJ_0(py_tv_clear_obj, py_tv_clear);
|
|
|
|
static const mp_rom_map_elem_t globals_dict_table[] = {
|
|
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_tv) },
|
|
{ MP_ROM_QSTR(MP_QSTR_TV_NONE), MP_ROM_INT(TV_NONE) },
|
|
{ MP_ROM_QSTR(MP_QSTR_TV_SHIELD), MP_ROM_INT(TV_SHIELD) },
|
|
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_tv_init_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&py_tv_deinit_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_tv_width_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_tv_height_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_tv_type_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_triple_buffer), MP_ROM_PTR(&py_tv_triple_buffer_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_tv_refresh_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_channel), MP_ROM_PTR(&py_tv_channel_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_display), MP_ROM_PTR(&py_tv_display_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_tv_clear_obj) },
|
|
};
|
|
|
|
static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table);
|
|
|
|
const mp_obj_module_t tv_module = {
|
|
.base = { &mp_type_module },
|
|
.globals = (mp_obj_t) &globals_dict,
|
|
};
|
|
|
|
void py_tv_init0() {
|
|
py_tv_deinit();
|
|
}
|
|
|
|
MP_REGISTER_MODULE(MP_QSTR_tv, tv_module);
|
|
|
|
#endif // MICROPY_PY_TV
|