diff --git a/src/omv/py/py_tv.c b/src/omv/py/py_tv.c new file mode 100644 index 000000000..fa03a5b40 --- /dev/null +++ b/src/omv/py/py_tv.c @@ -0,0 +1,637 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2018 kaizhi + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * TV Python module. + * + */ +#include +#include +#include +#include +#include "imlib.h" +#include "fb_alloc.h" +#include "ff_wrapper.h" +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" + +// Crystal frequency in MHZ (float, observe accuracy) +// In fact I use 28.63636 MHZ crystal and disable 8x PLL. +// But calculation worong if I change XTAL_MHZ value. +#define XTAL_MHZ 3.579545 +// Line length in microseconds (float, observe accuracy) +#define LINE_LENGTH_US 63.5555 +// Frame length in lines (visible lines + nonvisible lines) +// Amount has to be odd for NTSC and RGB colors +#define TOTAL_LINES 263 +// Number of lines used after the VSYNC but before visible area. +#define FRONT_PORCH_LINES 3 +// Width, in PLL clocks, of each pixel +// Used 4 to 8 for 160x120 pics +#define PLLCLKS_PER_PIXEL 9 // 4 is too short. +// Extra bytes can be added to end of picture lines to prevent pic-to-proto +// border artifacts. 8 is a good value. 0 can be tried to test, if there is +// no need for extra bytes. +#define BEXTRA 8 + +//// Protolines //// + +// Reserve memory for this number of different prototype lines +// (prototype lines are used for sync timing, porch and border area) +#define PROTOLINES 3 +// if your real protoline lenght is longer than one slot, you must +// use several slots per proto and there are total 16 slots +#define PROTOLINE_LENGTH_WORDS 512 + +// Protoline 0 starts always at address 0 +#define PROTOLINE_BYTE_ADDRESS(n) (PROTOLINE_LENGTH_WORDS) *2 *(n)) // 512 * 2 * n = 1024*n +#define PROTOLINE_WORD_ADDRESS(n) (PROTOLINE_LENGTH_WORDS * (n)) // 512 * n = 512*n + +// 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 +// 339 mV to 75 ohm load +#define BLACK_LEVEL 0x0066 +// Color burst +#define BURST_LEVEL (0x0d00 + BLACK_LEVEL) +#define WHITE_LEVEL 0x00ff + +//// NTSC video timing constants //// +// NTSC short sync duration is 2.542 us +#define SHORT_SYNC_US 2.542 +// 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-10.0/8.0)) +// For the middle of the line the whole duration of sync pulse is used. +#define SHORTSYNCM ((uint16_t)(SHORT_SYNC_US*XTAL_MHZ)) +// NTSC long sync duration is 27.3 us +#define LONG_SYNC_US 27.33275 +#define LONGSYNC ((uint16_t)(LONG_SYNC_US*XTAL_MHZ)) +#define LONGSYNCM ((uint16_t)(LONG_SYNC_US*XTAL_MHZ)) +// Normal visible picture line sync length is 4.7 us +#define SYNC_US 4.7 +#define SYNC ((uint16_t)(SYNC_US*XTAL_MHZ-10.0/8.0)) +// Color burst starts at 5.6 us +#define BURST_US 5.3 +#define BURST ((uint16_t)(BURST_US*XTAL_MHZ-10.0/8.0)) +// Color burst duration is 2.25 us +#define BURST_DUR_US 2.67 +#define BURSTDUR ((uint16_t)(BURST_DUR_US*XTAL_MHZ)) +// NTSC sync to blanking end time is 10.5 us +#define BLANK_END_US 9.155 +#define BLANKEND ((uint16_t)(BLANK_END_US*XTAL_MHZ-10.0/8.0)) +// Front porch starts at the end of the line, at 62.5us +#define FRPORCH_US 61.8105 +#define FRPORCH ((uint16_t)(FRPORCH_US*XTAL_MHZ-10.0/8.0)) + +/* +/// PAL video timing constants +/// PAL short sync duration is 2.35 us +#define SHORT_SYNC_US 2.35 +/// 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-10.0/8.0)) +/// For the middle of the line the whole duration of sync pulse is used. +#define SHORTSYNCM ((uint16_t)(SHORT_SYNC_US*XTAL_MHZ)) +/// PAL long sync duration is 27.3 us +#define LONG_SYNC_US 27.3 +#define LONGSYNC ((uint16_t)(LONG_SYNC_US*XTAL_MHZ)) +#define LONGSYNCM ((uint16_t)(LONG_SYNC_US*XTAL_MHZ)) +/// Normal visible picture line sync length is 4.7 us +#define SYNC_US 4.7 +#define SYNC ((uint16_t)(SYNC_US*XTAL_MHZ-10.0/8.0)) +/// Color burst starts at 5.6 us +#define BURST_US 5.6 +#define BURST ((uint16_t)(BURST_US*XTAL_MHZ-10.0/8.0)) +/// Color burst duration is 2.25 us +#define BURST_DUR_US 2.25 +#define BURSTDUR ((uint16_t)(BURST_DUR_US*XTAL_MHZ)) +/// PAL sync to blanking end time is 10.5 us +#define BLANK_END_US 10.5 +#define BLANKEND ((uint16_t)(BLANK_END_US*XTAL_MHZ-10.0/8.0)) +/// Front porch starts at the end of the line, at 62.5us +#define FRPORCH_US 62.5 +#define FRPORCH ((uint16_t)(FRPORCH_US*XTAL_MHZ-10.0/8.0)) +*/ + +//// Definitions for picture lines //// + +// On which line the picture area begins, the Y direction. +#define STARTLINE (FRONT_PORCH_LINES + 18) +#define YPIXELS 120 +// The last picture area line +#define ENDLINE STARTLINE + YPIXELS +// The first pixel of the picture area, the X direction. +#define STARTPIX (BLANKEND+6) +// The last pixel of the picture area. Set PIXELS to wanted value and suitable +// ENDPIX value is calculated. +#define XPIXELS 160 +#define ENDPIX ((uint16_t)(STARTPIX+PLLCLKS_PER_PIXEL*XPIXELS/8)) + +// PLL frequency +#define PLL_MHZ (XTAL_MHZ * 8.0) +// 10 first pllclks, which are not in the counters are dePICLINE_LENGTH_BYTEScremented here +#define PLLCLKS_PER_LINE ((uint16_t)((LINE_LENGTH_US * PLL_MHZ)+0.5-10)) +// 10 first pllclks, which are not in the counters are decremented here +#define COLORCLKS_PER_LINE ((uint16_t)((LINE_LENGTH_US * XTAL_MHZ)+0.5-10.0/8.0)) +#define COLORCLKS_LINE_HALF ((uint16_t)((LINE_LENGTH_US * XTAL_MHZ)/2+0.5-10.0/8.0)) + +// Calculate picture lengths in pixels and bytes, coordinate areas for picture area +#define PICLENGTH (ENDPIX - STARTPIX) +#define PICX ((uint16_t)(PICLENGTH * 8 / PLLCLKS_PER_PIXEL)) +#define PICY (ENDLINE-STARTLINE) + +#define PICLINE_LENGTH_BYTES (XPIXELS*2) +// Picture area memory start point +#define PICLINE_START ((INDEX_START_BYTES + TOTAL_LINES*3+1)+1) + +// Picture area line start addresses +#define PICLINE_WORD_ADDRESS(n) (PICLINE_START/2+(PICLINE_LENGTH_BYTES/2+BEXTRA/2)*(n)) +#define PICLINE_BYTE_ADDRESS(n) ((uint32_t)(PICLINE_START+((uint32_t)(PICLINE_LENGTH_BYTES)+BEXTRA)*(n))) + + + +//// Index start ///// + +#define PROTO_AREA_WORDS (PROTOLINE_LENGTH_WORDS * PROTOLINES) +#define INDEX_START_LONGWORDS ((PROTO_AREA_WORDS+1)/2) +#define INDEX_START_WORDS (INDEX_START_LONGWORDS * 2) +#define INDEX_START_BYTES (INDEX_START_WORDS * 2) + +//// Pattern generator microcode //// + +// Bits 7:6 +#define PICK_A (0<<6) // 00=a +#define PICK_B (1<<6) // 01=b +#define PICK_Y (2<<6) // 10=y +#define PICK_NOTHING (3<<6) // 11=- + +// Bits 5:3 +// Pick 1..8 +#define PICK_BITS(a) (((a)-1)<<3) + +// Bits 2:0 +// Shift 0..6 +#define SHIFT_BITS(a) (a) + +// The microcode is given as a 32-bit parameter to the SpiWrite-function, and must +// therefore be typecasted to unsigned long. Otherwise, if using Arduino, the values +// shifted beyond 16-bit range are lost. VS1005 and VS1010 would not require typecasting +// in this instance. +// b=>u +// a=>v +// y=>luminance +#define OP1 (unsigned long)(PICK_B + PICK_BITS(4) + SHIFT_BITS(4)) +#define OP2 (unsigned long)(PICK_A + PICK_BITS(4) + SHIFT_BITS(4)) +#define OP3 (unsigned long)(PICK_Y + PICK_BITS(8) + SHIFT_BITS(6)) +#define OP4 (unsigned long)(PICK_NOTHING + SHIFT_BITS(2)) + +//// VS23 SPI Commands //// + +// General VS23 commands +#define WRITE_STATUS 0x01 // Write Status Register +#define WRITE 0x02 // Write SRAM +#define READ 0x03 // Read SRAM +#define WRITE_MULTIIC 0xb8 // Write Multi-IC Access Control +#define READ_MULTIIC 0xb7 // Read Multi-IC Access Control +#define READ_ID 0x9f // Read Manufacturer and Device ID + + +// Bit definitions +#define VDCTRL1 0x2B +#define VDCTRL1_UVSKIP (1<<0) +#define VDCTRL1_DACDIV (1<<3) +#define VDCTRL1_PLL_ENABLE (1<<12) +#define VDCTRL1_SELECT_PLL_CLOCK (1<<13) +#define VDCTRL1_USE_UVTABLE (1<<14) +#define VDCTRL1_DIRECT_DAC (1<<15) + +#define VDCTRL2 0x2D +#define VDCTRL2_LINECOUNT ( (TOTAL_LINES-1) << 0) +#define VDCTRL2_PROGRAM_LENGTH ((PLLCLKS_PER_PIXEL-1)<<10) +#define VDCTRL2_NTSC (0<<14) +#define VDCTRL2_PAL (1<<14) +#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 LINELEN_VGP_OUTPUT (1<<15) +#define YUVBITS 0x2b +#define INDEXSTART 0x2c +#define LINECFG 0x2d +#define VTABLE 0x2e +#define UTABLE 0x2f +#define BLOCKMVC1 0x34 +#define CURLINE 0x53 +#define GPIOCTL 0x82 + +#define RST_PORT GPIOD +#define RST_PIN GPIO_PIN_12 +#define RST_PIN_WRITE(bit) HAL_GPIO_WritePin(RST_PORT, RST_PIN, bit); + +#define CS_PORT GPIOB +#define CS_PIN GPIO_PIN_12 +#define CS_PIN_WRITE(bit) HAL_GPIO_WritePin(CS_PORT, CS_PIN, bit); + +extern mp_obj_t pyb_spi_send(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +//extern mp_obj_t pyb_spi_send_recv(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t pyb_spi_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args); +extern mp_obj_t pyb_spi_deinit(mp_obj_t self_in); + +static mp_obj_t spi_port = NULL; +static enum { TV_NONE, TV_SHIELD } type = TV_NONE; + +static mp_obj_t SpiSendByte(register uint16_t b) { + mp_map_t arg_map; + arg_map.all_keys_are_qstrs = true; + arg_map.is_fixed = true; + arg_map.is_ordered = true; + arg_map.used = 0; + arg_map.alloc = 0; + arg_map.table = NULL; + mp_obj_t result; + result = pyb_spi_send( + 2, (mp_obj_t []) { + spi_port, + mp_obj_new_int(b) + }, + &arg_map + ); + return result; +} +static mp_obj_t SpiSendLine(uint8_t *line, uint16_t length) { + mp_map_t arg_map; + arg_map.all_keys_are_qstrs = true; + arg_map.is_fixed = true; + arg_map.is_ordered = true; + arg_map.used = 0; + arg_map.alloc = 0; + arg_map.table = NULL; + pyb_spi_send( + 2, (mp_obj_t []) { + spi_port, + mp_obj_new_bytes(line, length) + }, + &arg_map + ); + return mp_const_none; +} +static mp_obj_t SpiSendWord(register uint16_t b) { + mp_map_t arg_map; + arg_map.all_keys_are_qstrs = true; + arg_map.is_fixed = true; + arg_map.is_ordered = true; + arg_map.used = 0; + arg_map.alloc = 0; + arg_map.table = NULL; + mp_obj_t result; + + uint8_t data[2] = {b >> 8, b & 0xff}; + + result = pyb_spi_send( + 2, (mp_obj_t []) { + spi_port, + mp_obj_new_bytes(data, 2) + }, + &arg_map + ); + return result; +} +// SpiWrite uses SPI to write the VS23 registers, and to write VS23 SRAM +// addresses. +// The opcode-parameter determines the performed action. The opcodes +// have been defined, and their descriptions can be found in the +// VS23 datasheet. +// The address-parameter is only used when writing to SRAM with WRITE opcode. +// The data-parameter is used when writing to registers or memory. Generally +// only one or two bytes are written, with the exception of writing +// the microcode (PROGRAM opcode). +// The is16b-parameter must be non-zero if the write OR read value +// is a 16-bit word. Otherwise only a byte of the sent or received data is +// used. With the PROGRAM opcode the is16b parameter does not affect any +// functionality, and can be set to any value. +// If a register read opcode was given, the data will be set to the returned +// result-variable. +static mp_obj_t SpiWrite(register uint16_t opcode, register uint32_t address, + register uint32_t data, uint16_t is16b) +{ + mp_obj_t result = mp_const_none; + CS_PIN_WRITE(false); + SpiSendByte(opcode); + + // Write the microcode, 4 bytes. + // No need for result because operation is always a write. + // The regular data write/read functions are skipped with the goto. + if (opcode == PROGRAM) { + SpiSendWord(data >> 16); + SpiSendWord(data); + goto END; // I don't like goto, but I don't want to change it. + + // With an SRAM write, the address is sent first. Data will be sent after + // the else-if structure (goto END is not used, unlike with PROGRAM). + } else if ( (opcode == WRITE) | (opcode == READ) ) { + if (is16b) address = address << 1; + // SRAM is 131072 bytes, making the last address 0x1FFFF (17 bits) + SpiSendByte((address >> 16)); + SpiSendWord((address)); + } + + // Send or receive either one or two bytes of data. If a register or SRAM + // write is done, the result-variable will not receive any return values. + // Similarly, if a read is done the data-variable should be 0. + if (is16b) result = SpiSendWord(data); + else result = SpiSendByte(data); + +END: + CS_PIN_WRITE(true); + return result; // Can be ignored if a write operation was done. +} +void protoline(uint16_t line, uint16_t offset, uint16_t limit, uint16_t data) +{ + uint16_t i = 0; + uint16_t w = PROTOLINE_WORD_ADDRESS(line) + offset; + if (offset == BLANKEND) i = BLANKEND; + for (; i<=limit; i++) SpiWrite(WRITE, (uint16_t)w++, data, 1); +} +void SetLineIndex(uint16_t line, uint16_t wordAddress) +{ + uint32_t indexAddr = INDEX_START_BYTES + line*3; + SpiWrite(WRITE, indexAddr++, 0, 0); + SpiWrite(WRITE, indexAddr++, wordAddress, 0); + SpiWrite(WRITE, indexAddr++, wordAddress >> 8, 0); +} +void SetPicIndex(uint16_t line, uint32_t byteAddress, uint16_t protoAddress) +{ + uint32_t indexAddr = INDEX_START_BYTES + line*3; + SpiWrite(WRITE, indexAddr++, ((byteAddress << 7) & 0x80) | (protoAddress & 0xf), 0); + SpiWrite(WRITE, indexAddr++, (byteAddress >> 1), 0); + SpiWrite(WRITE, indexAddr, (byteAddress >> 9), 0); +} +// Draws a filled rectangle, with the specified color, from coordinates (x1,y1) to (x2,y2) +void FilledRectangle (uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { + uint16_t width; + uint32_t address; + + if (y1 >= PICY) return; // If starting y-coordinate is past the picture borders, nothing is done + if (y2 >= PICY) y2=PICY-1; // The rectangle stops at the bottom border of the image + + width = (x2-x1)+1; + if (width>400) width=400; + + // Loops through one horizontal line at a time. + while (y1 <= y2) { + address = PICLINE_BYTE_ADDRESS(y1) + x1; + CS_PIN_WRITE(false); + SpiSendByte(WRITE); + SpiSendByte(address >> 16); + SpiSendWord(address); + + // Color values of each x coordinate of the horizontal line + for (int i=0; i 8 || channel < 1) + { + PY_ASSERT_TRUE_MSG(false, "channel should range 1~8"); + } + uint8_t data = (channel-1) | 0xF0; + SpiWrite(GPIOCTL, 0, data, 0); + return mp_const_none; +} +static mp_obj_t py_tv_display(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + PY_ASSERT_TRUE_MSG(IM_IS_MUTABLE(arg_img), "Image format is not supported."); + + rectangle_t rect; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &rect); + + const uint16_t x1 = rect.x; + const uint16_t y1 = rect.y; + const uint16_t w = rect.w < XPIXELS? rect.w : XPIXELS; + const uint16_t h = rect.h < YPIXELS? rect.h : YPIXELS; + const uint16_t y2 = y1 + h; + + uint32_t address; + uint16_t x = x1; + uint16_t y = y1; + + uint8_t *line = fb_alloc(w*2); + + while (y < y2) { + address = PICLINE_BYTE_ADDRESS(y) + x1; + CS_PIN_WRITE(false); + SpiSendByte(WRITE); + SpiSendByte(address >> 16); + SpiSendWord(address); + for(int i = 0; i < w; i++) + { + x = x1 + i; + if (IM_IS_GS(arg_img)) { + line[2*i] = 0; + line[2*i + 1] = IM_GET_GS_PIXEL(arg_img, x, y);; + } else { + // b=>u + // a=>v + // y=>luminance + uint16_t pixel = IM_GET_RGB565_PIXEL(arg_img, x, y); + uint8_t b4 = (COLOR_RGB565_TO_U(pixel)) & 0xF0; + uint8_t a4 = ((-COLOR_RGB565_TO_V(pixel))>>4) & 0x0F; + uint8_t y8 = ((COLOR_RGB565_TO_Y(pixel)+128)); + line[2*i] = b4 | a4; + line[2*i + 1] = y8; + } + } + SpiSendLine(line, w*2); + CS_PIN_WRITE(true); + y++; + } + fb_free(); + return mp_const_none; +} +static mp_obj_t py_tv_palettes() +{ + for (int i=0; i<16; i++) for (int j=0; j<16; j++) { + FilledRectangle((i*20), (j*10), (i*20)+19, (j*10)+9, (j*16)+i); // Draw colored rectangles + FilledRectangle((i*20), (j*10)+9, (i*20)+19, (j*10)+9, 0); // Draw black horizontal line + FilledRectangle((i*20)+19, (j*10), (i*20)+19, (j*10)+9, 0); // Draw black vertical line + } + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_init_obj, 0, py_tv_init); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_deinit_obj, py_tv_deinit); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tv_channel_obj, py_tv_channel); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_type_obj, py_tv_type); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_display_obj, 1, py_tv_display); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_palettes_obj, py_tv_palettes); +static const mp_map_elem_t globals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_tv) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&py_tv_init_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_deinit), (mp_obj_t)&py_tv_deinit_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_channel), (mp_obj_t)&py_tv_channel_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_type), (mp_obj_t)&py_tv_type_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_display), (mp_obj_t)&py_tv_display_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_palettes), (mp_obj_t)&py_tv_palettes_obj }, + { NULL, NULL }, +}; +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(); +} diff --git a/src/omv/py/py_tv.h b/src/omv/py/py_tv.h new file mode 100644 index 000000000..386bc901e --- /dev/null +++ b/src/omv/py/py_tv.h @@ -0,0 +1,12 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * LCD Python module. + * + */ +#ifndef __PY_TV_H__ +#define __PY_TV_H__ +void py_tv_init0(); +#endif // __PY_TV_H__ diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 2332ec25b..9f26e34d0 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -79,6 +79,13 @@ Q(get_backlight) Q(display) Q(clear) +// tv Module +Q(tv) +Q(channel) +Q(type) +Q(display) +Q(palettes) + // Gif module Q(gif) Q(Gif)