From 6afa5243051ff60072e5a24e182092687a641ccd Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Wed, 25 Nov 2020 20:40:46 -0800 Subject: [PATCH] Add hdmi cec support --- src/Makefile | 1 + src/omv/Makefile | 1 + .../OPENMVPURETHERMAL/omv_boardconfig.h | 4 + src/omv/py/py_lcd.c | 101 ++- src/omv/py/py_lcd_cec.c | 596 ++++++++++++++++++ src/omv/py/py_lcd_cec.h | 20 + src/omv/py/qstrdefsomv.h | 5 + 7 files changed, 709 insertions(+), 19 deletions(-) create mode 100644 src/omv/py/py_lcd_cec.c create mode 100644 src/omv/py/py_lcd_cec.h diff --git a/src/Makefile b/src/Makefile index 64733ff3c..55b815697 100755 --- a/src/Makefile +++ b/src/Makefile @@ -310,6 +310,7 @@ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/py/, \ py_image.o \ py_time.o \ py_lcd.o \ + py_lcd_cec.o \ py_lcd_touch.o \ py_tv.o \ py_fir.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index 46f759f38..0ef4dcc70 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -97,6 +97,7 @@ SRCS += $(addprefix py/, \ py_image.c \ py_time.c \ py_lcd.c \ + py_lcd_cec.c \ py_lcd_touch.c \ py_tv.c \ py_fir.c \ diff --git a/src/omv/boards/OPENMVPURETHERMAL/omv_boardconfig.h b/src/omv/boards/OPENMVPURETHERMAL/omv_boardconfig.h index 60bcc0162..b56e20965 100644 --- a/src/omv/boards/OPENMVPURETHERMAL/omv_boardconfig.h +++ b/src/omv/boards/OPENMVPURETHERMAL/omv_boardconfig.h @@ -580,6 +580,10 @@ #define OMV_TOUCH_SDA_PIN (pin_J14) #define OMV_TOUCH_INT_PIN (pin_J6) +// HDMI CEC I/O +#define OMV_CEC_PRESENT +#define OMV_CEC_PIN (pin_H2) + // Enable additional GPIO banks for DRAM... #define OMV_ENABLE_GPIO_BANK_F #define OMV_ENABLE_GPIO_BANK_G diff --git a/src/omv/py/py_lcd.c b/src/omv/py/py_lcd.c index 5c18b2d4e..2611132da 100644 --- a/src/omv/py/py_lcd.c +++ b/src/omv/py/py_lcd.c @@ -11,6 +11,7 @@ #include STM32_HAL_H #include "extint.h" #include "spi.h" +#include "py_lcd_cec.h" #include "py_lcd_touch.h" #include "py_helper.h" #include "extmod/machine_i2c.h" @@ -1208,7 +1209,12 @@ STATIC mp_obj_t py_lcd_deinit() if ((lcd_type == LCD_DISPLAY) || (lcd_type == LCD_DISPLAY_WITH_HDMI)) ltdc_set_backlight(0); // back to default state #endif // OMV_LCD_BL_PIN #ifdef OMV_DVI_PRESENT - if ((lcd_type == LCD_DISPLAY_WITH_HDMI) || (lcd_type == LCD_DISPLAY_ONLY_HDMI)) ltdc_dvi_deinit(); + if ((lcd_type == LCD_DISPLAY_WITH_HDMI) || (lcd_type == LCD_DISPLAY_ONLY_HDMI)) { + ltdc_dvi_deinit(); + #ifdef OMV_CEC_PRESENT + lcd_cec_deinit(); + #endif + } #endif // OMV_DVI_PRESENT #ifdef OMV_TOUCH_PRESENT if ((lcd_type == LCD_DISPLAY) || (lcd_type == LCD_DISPLAY_WITH_HDMI)) lcd_touch_deinit(); @@ -1275,7 +1281,12 @@ STATIC mp_obj_t py_lcd_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_args if ((type == LCD_DISPLAY) || (type == LCD_DISPLAY_WITH_HDMI)) ltdc_set_backlight(255); // to on state #endif // OMV_LCD_BL_PIN #ifdef OMV_DVI_PRESENT - if ((type == LCD_DISPLAY_WITH_HDMI) || (type == LCD_DISPLAY_ONLY_HDMI)) ltdc_dvi_init(); + if ((type == LCD_DISPLAY_WITH_HDMI) || (type == LCD_DISPLAY_ONLY_HDMI)) { + ltdc_dvi_init(); + #ifdef OMV_CEC_PRESENT + lcd_cec_init(); + #endif + } #endif // OMV_DVI_PRESENT #ifdef OMV_TOUCH_PRESENT if ((type == LCD_DISPLAY) || (type == LCD_DISPLAY_WITH_HDMI)) lcd_touch_init(); @@ -1382,34 +1393,62 @@ STATIC mp_obj_t py_lcd_get_backlight() } STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_backlight_obj, py_lcd_get_backlight); +#ifdef OMV_DVI_PRESENT STATIC mp_obj_t py_lcd_get_display_connected() { - #ifdef OMV_DVI_PRESENT return ltdc_dvi_get_display_connected(); - #else - return mp_const_false; - #endif } STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_display_connected_obj, py_lcd_get_display_connected); -STATIC mp_obj_t py_lcd_get_display_id_data() -{ - #if defined(OMV_DVI_PRESENT) && defined(OMV_DDC_PRESENT) - return ltdc_dvi_get_display_id_data(); - #else - return mp_const_empty_bytes; - #endif -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_display_id_data_obj, py_lcd_get_display_id_data); - STATIC mp_obj_t py_lcd_register_hotplug_cb(mp_obj_t cb) { - #ifdef OMV_DVI_PRESENT ltdc_dvi_register_hotplug_cb(cb); - #endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_register_hotplug_cb_obj, py_lcd_register_hotplug_cb); +#endif + +#if defined(OMV_DVI_PRESENT) && defined(OMV_DDC_PRESENT) +STATIC mp_obj_t py_lcd_get_display_id_data() +{ + return ltdc_dvi_get_display_id_data(); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_display_id_data_obj, py_lcd_get_display_id_data); +#endif + +#if defined(OMV_DVI_PRESENT) && defined(OMV_CEC_PRESENT) +STATIC mp_obj_t py_lcd_send_frame(mp_obj_t dst_addr, mp_obj_t src_addr, mp_obj_t bytes) +{ + lcd_cec_send_frame(dst_addr, src_addr, bytes); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_lcd_send_frame_obj, py_lcd_send_frame); + +STATIC mp_obj_t py_lcd_receive_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return lcd_cec_receive_frame(n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_receive_frame_obj, 1, py_lcd_receive_frame); + +STATIC mp_obj_t py_lcd_register_cec_receive_cb(mp_obj_t cb, mp_obj_t dst_addr) +{ + lcd_cec_register_cec_receive_cb(cb, dst_addr); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_lcd_register_cec_receive_cb_obj, py_lcd_register_cec_receive_cb); + +STATIC mp_obj_t py_lcd_received_frame_src_addr() +{ + return lcd_cec_received_frame_src_addr(); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_received_frame_src_addr_obj, py_lcd_received_frame_src_addr); + +STATIC mp_obj_t py_lcd_received_frame_bytes() +{ + return lcd_cec_received_frame_bytes(); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_received_frame_bytes_obj, py_lcd_received_frame_bytes); +#endif #ifdef OMV_TOUCH_PRESENT STATIC mp_obj_t py_lcd_update_touch_points() @@ -1421,6 +1460,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_update_touch_points_obj, py_lcd_update_t STATIC mp_obj_t py_lcd_register_touch_cb(mp_obj_t cb) { lcd_touch_register_touch_cb(cb); + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_register_touch_cb_obj, py_lcd_register_touch_cb); @@ -1460,6 +1500,7 @@ STATIC mp_obj_t py_lcd_get_point_y_position(mp_obj_t index) } STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_get_point_y_position_obj, py_lcd_get_point_y_position); #endif + STATIC mp_obj_t py_lcd_display(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); @@ -1660,9 +1701,31 @@ STATIC const mp_rom_map_elem_t globals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_lcd_refresh_obj) }, { MP_ROM_QSTR(MP_QSTR_get_backlight), MP_ROM_PTR(&py_lcd_get_backlight_obj) }, { MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&py_lcd_set_backlight_obj) }, +#ifdef OMV_DVI_PRESENT { MP_ROM_QSTR(MP_QSTR_get_display_connected), MP_ROM_PTR(&py_lcd_get_display_connected_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_display_id_data), MP_ROM_PTR(&py_lcd_get_display_id_data_obj) }, { MP_ROM_QSTR(MP_QSTR_register_hotplug_cb), MP_ROM_PTR(&py_lcd_register_hotplug_cb_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_get_display_connected), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_hotplug_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, +#endif +#if defined(OMV_DVI_PRESENT) && defined(OMV_DDC_PRESENT) + { MP_ROM_QSTR(MP_QSTR_get_display_id_data), MP_ROM_PTR(&py_lcd_get_display_id_data_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_get_display_id_data), MP_ROM_PTR(&py_func_unavailable_obj) }, +#endif +#if defined(OMV_DVI_PRESENT) && defined(OMV_CEC_PRESENT) + { MP_ROM_QSTR(MP_QSTR_send_frame), MP_ROM_PTR(&py_lcd_send_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_receive_frame), MP_ROM_PTR(&py_lcd_receive_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_receive_cb), MP_ROM_PTR(&py_lcd_register_cec_receive_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_received_frame_src_addr), MP_ROM_PTR(&py_lcd_received_frame_src_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_received_frame_bytes), MP_ROM_PTR(&py_lcd_received_frame_bytes_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_send_frame), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_receive_frame), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_receive_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_received_frame_src_addr), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_received_frame_bytes), MP_ROM_PTR(&py_func_unavailable_obj) }, +#endif #ifdef OMV_TOUCH_PRESENT { MP_ROM_QSTR(MP_QSTR_update_touch_points), MP_ROM_PTR(&py_lcd_update_touch_points_obj) }, { MP_ROM_QSTR(MP_QSTR_register_touch_cb), MP_ROM_PTR(&py_lcd_register_touch_cb_obj) }, diff --git a/src/omv/py/py_lcd_cec.c b/src/omv/py/py_lcd_cec.c new file mode 100644 index 000000000..f903a5782 --- /dev/null +++ b/src/omv/py/py_lcd_cec.c @@ -0,0 +1,596 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * LCD Python module. + */ +#include "extint.h" +#include "objarray.h" +#include "py_helper.h" +#include "py_lcd_cec.h" +#include "omv_boardconfig.h" + +#ifdef OMV_CEC_PRESENT +static mp_obj_t lcd_cec_user_cb = NULL; + +static volatile uint8_t lcd_cec_src_addr = 0; +static volatile mp_obj_t lcd_cec_dst_addr = NULL; +static volatile mp_obj_t lcd_cec_bytes = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// stsw-stm32086 - https://www.st.com/en/embedded-software/stsw-stm32086.html +//////////////////////////////////////////////////////////////////////////////// + +/* Start bit timings */ +#define Sbit_Nom_LD 37 /* Start Bit Nominal Low duration: 37 x 100µs = 3.7ms */ +#define Sbit_Nom_HD 8 /* Start Bit Nominal High Duration: 8 x 100µs = 0.8ms */ +#define Sbit_Min_LD 35 /* Start Bit Minimum Low duration: 35 x 100µs = 3.5ms */ +#define Sbit_Max_LD 39 /* Start Bit Maximum Low duration: 39 x 100µs = 3.9ms */ +#define Sbit_Min_TD 43 /* Start Bit Minimum Total duration: 43 x 100µs = 4.3ms */ +#define Sbit_Max_TD 47 /* Start Bit Maximum Total duration: 47 x 100µs = 4.7ms */ + +/* Data bit logical "0" timings */ +#define Dbit0_Nom_LD 15 /* Data Bit "0" Nominal Low duration: 15 x 100µs = 1.5ms */ +#define Dbit0_Nom_HD 9 /* Data Bit "0" Nominal High Duration: 9 x 100µs = 0.9ms */ +#define Dbit0_Min_LD 13 /* Data Bit "0" Minimum Low duration: 13 x 100µs = 1.3ms */ +#define Dbit0_Max_LD 17 /* Data Bit "0" Maximum Low duration: 17 x 100µs = 1.7ms */ + +/* Data bit logical "1" timings */ +#define Dbit1_Nom_LD 6 /* Data Bit (logical "1") Nominal Low duration: 0.6ms */ +#define Dbit1_Nom_HD 18 /* Data Bit (logical "1") Nominal High Duration: 1.8ms */ +#define Dbit1_Min_LD 4 /* Data Bit "1" Minimum Low duration: 0.4ms */ +#define Dbit1_Max_LD 8 /* Data Bit "1" Maximum Low duration: 0.8ms */ + +/* Data bit duration */ +#define DbitX_Min_TD 20 /* Data Bit Minimum Total duration: 2ms */ +#define DbitX_Max_TD 27 /* Data Bit Maximum Total duration: 2.7ms */ + +/* Header or Data block definition */ +#define DataBlock 0 +#define HeaderBlock 1 + +/* Masks */ +#define ReceiveFrameStatusMask 0x00010000 +#define FrameSendToMeMask 0x00020000 +#define InitiatorAddressMask 0x000000FF + +static uint8_t cec_byte; +static uint8_t cec_bit; +static uint8_t cec_eom; +static uint32_t cec_counter; +static uint8_t cec_last_byte; + +static void CEC_Wait100us(uint32_t nTime) +{ + mp_hal_delay_us(nTime * 100); +} + +static void CEC_SendStartBit() +{ + /* Start Low Period */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_RESET); + + /* Wait 3.7ms*/ + CEC_Wait100us(Sbit_Nom_LD); + + /* Start High Period */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); + + /* Wait 0.8ms for a total of 3.7 + 0.8 = 4.5ms */ + CEC_Wait100us(Sbit_Nom_HD); +} + +static uint8_t CEC_ReceiveStartBit() +{ + /* Initialize cec_counter */ + cec_counter = 0; + + /* Go to high impedance: CEC bus state = VDD */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); + + /* Wait for rising edge of the start bit */ + while (!HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) + { + /* Wait 100us */ + CEC_Wait100us(1); + + /* Increment cec_counter that contains the duration of low level duration */ + cec_counter++; + + /* If too long low level for start bit */ + if (cec_counter > Sbit_Max_LD) + { + /* Exit: it's an error: 0 */ + return 0; + } + } + + /* If too short duration of low level for start bit */ + if (cec_counter < Sbit_Min_LD) + { + /* Exit: it's an error: 0 */ + return 0; + } + + /* Wait for falling edge of the start bit (end of start bit) */ + while (HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) + { + /* Wait 100us */ + CEC_Wait100us(1); + + /* Increment cec_counter that contains the duration of high level duration */ + cec_counter++; + + /* If too long total duration for start bit */ + if (cec_counter > Sbit_Max_TD) + { + /* Exit: it's an error: 0 */ + return 0; + } + } + + /* If too short total duration for start bit */ + if (cec_counter < Sbit_Min_TD) + { + /* Exit: it's an error: 0 */ + return 0; + } + + /* Exit: start bit received correctly */ + return 1; +} + +static void CEC_SendDataBit(uint8_t bit) +{ + /* Start Low Period: the duration depends on the Logical Level "0" or "1" */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_RESET); + + /* Wait 0.6 ms if Logical Level is "1" and 1.5ms if Logical Level is "0" */ + CEC_Wait100us(bit ? Dbit1_Nom_LD : Dbit0_Nom_LD); + + /* Start High Period: the duration depends on the Logical Level "0" or "1" */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); + + /* Wait 1.8 ms if Logical Level is "1" and 0.9 ms if Logical Level is "0" */ + CEC_Wait100us(bit ? Dbit1_Nom_HD : Dbit0_Nom_HD); +} + +static uint8_t CEC_ReceiveDataBit() +{ + uint8_t bit = 0xFF; + + /* Initialize cec_counter */ + cec_counter = 0; + + /* Wait for rising edge of the data bit */ + while (!HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) + { + /* Wait 100us */ + CEC_Wait100us(1); + + /* Increment cec_counter that contains the duration of low level duration */ + cec_counter++; + + if (cec_counter > Dbit0_Max_LD) + { + /* Exit: it's an error: 0xFF */ + return 0xFF; + } + } + + /* If the measured duration of the low level is greater than the minimum low duration + of "logical 0" i.e > 1.3 ms */ + if (cec_counter > Dbit0_Min_LD) + { + /* The received bit is "logical 0" */ + bit = 0; + } + else + { + /* If the measured duration of the low level is greater than the maximum low duration + of "logical 1" i.e > 0.8 ms */ + if (cec_counter > Dbit1_Max_LD) + { + /* Exit: it's an error: 0xFF */ + return 0xFF; + } + + /* If the measured duration of the low level is greater than the minimum low duration + of "logical 1" i.e > 0.4 ms */ + if (cec_counter > Dbit1_Min_LD) + { + /* The received bit is "logical 1" */ + bit = 1; + } + else + { + /* Exit: it's an error: 0xFF */ + return 0xFF; + } + } + + /* Wait for falling edge of the data bit */ + while (HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) + { + /* Wait 100us */ + CEC_Wait100us(1); + + /* Increment cec_counter that contains the duration of high level duration */ + cec_counter++; + + /* If too long total duration for the data bit is detected */ + if (cec_counter > DbitX_Max_TD) + { + /* Exit: it's an error: 0xFF */ + return 0xFF; + } + } + + /* If too short total duration for the data bit is detected */ + if (cec_counter < DbitX_Min_TD) + { + /* Exit: it's an error: 0xFF */ + return 0xFF; + } + + /* The data bit is received correctly. Return its value */ + return bit; +} + +static void CEC_SendAckBit() +{ + mp_uint_t start = mp_hal_ticks_ms(); + /* Wait for falling edge: end of EOM bit sent by the initiator */ + while (HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) { + if ((mp_hal_ticks_ms() - start) > 10) nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Ack timeout!")); + } + + /* Send ACK bit */ + CEC_SendDataBit(0); + + /* Force the bus to 0: for ACK bit delimiting */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_RESET); + + /* Wait 100us to allow the initiator to detect the end of ACK bit */ + CEC_Wait100us(1); + + /* Go to high impedance: CEC bus state = VDD */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); +} + +static uint8_t CEC_ReceiveAckBit() +{ + uint8_t AckValue = 0xFF; + + /* Get the ACK bit */ + AckValue = CEC_ReceiveDataBit(); + + /* If the byte has been acknowledged by the Follower (ACK = 0) */ + if (AckValue != 0xFF) + { + mp_uint_t start = mp_hal_ticks_ms(); + /* Wait for falling edge of ACK bit (the end of ACK bit)*/ + while (HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) { + if ((mp_hal_ticks_ms() - start) > 10) nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Ack timeout!")); + } + } + + return AckValue; +} + +static bool CEC_SendByte(uint8_t byte) +{ + /* Send the data byte: bit by bit (MSB first) */ + for (cec_bit = 0; cec_bit <= 7; cec_bit++) + { + CEC_SendDataBit(byte & 0x80); + byte <<= 1; + } + + /* Send EOM bit after sending the data byte */ + CEC_SendDataBit(cec_last_byte); + + /* Force the bus to 0: for EOM bit delimiting */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_RESET); + + /* Wait 100us to allow the follower to detect the end of EOM bit */ + CEC_Wait100us(1); + + /* Go to high impedance: CEC bus state = VDD */ + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); + + /* If the byte is acknowledged by the receiver */ + if (CEC_ReceiveAckBit() == 0) + { + /* Exit: the data byte has been received by the follower */ + return true; + } + else /* The data byte is not acknowledged by the follower */ + { + /* Exit: the data byte has not been received by the follower */ + return false; + } +} + +static uint8_t CEC_ReceiveByte(uint8_t HeaderDataIndicator) +{ + uint8_t TempReceiveBit = 0; + bool ReceiveByteStatus = true; + + /* Initialize cec_counter */ + cec_byte = 0; + + for (cec_bit = 0; cec_bit <= 7; cec_bit++) + { + /* Shift the data byte */ + cec_byte <<= 1; + + /* Receive the data bit */ + TempReceiveBit = CEC_ReceiveDataBit(); + + /* If the received bit was received incorrectly */ + if (TempReceiveBit == 0xFF) /* If the received bit is wrong */ + { + /* Store the error status */ + ReceiveByteStatus = false; + } + + /* Build the data byte (MSB first) */ + cec_byte |= TempReceiveBit; + } + + /* Read EOM bit */ + cec_eom = CEC_ReceiveDataBit(); + + /* If the EOM bit was received incorrectly */ + if (cec_eom == 0xFF) /* If the received bit is wrong */ + { + /* Store the error status */ + ReceiveByteStatus = false; + } + + /* If the byte to send is a "Data" block */ + if (HeaderDataIndicator == DataBlock) + { + /* If the bits has been received correctly, acknowledge the data byte */ + if (ReceiveByteStatus != false) + { + /* Send the Ack bit */ + CEC_SendAckBit(); + + /* Byte received correctly: return 1 */ + return 1; + } + else /* Otherwise do not acknowledge the received byte */ + { + /* Byte received incorrectly: return 0 */ + return 0; + } + } + else /* If the byte is a "Header" block */ + { + /* If the bits has been received correctly and do not acknowledge the byte */ + if (ReceiveByteStatus != false) + { + /* Byte received correctly: return 1 */ + return 1; + } + else /* If the bits has been received incorrectly and do not acknowledge the byte */ + { + /* Byte received incorrectly: return 0 */ + return 0; + } + } +} + +static bool CEC_SendFrame(uint8_t InitiatorAddress, uint8_t FollowerAddress, uint8_t MessageLength, uint8_t *Message) +{ + uint8_t i = 0; + uint8_t HeaderBlockValueToSend = 0; + + cec_last_byte = 0; + + /* Build the Header block to send */ + HeaderBlockValueToSend = (((InitiatorAddress & 0xF) << 4) | (FollowerAddress & 0xF)); + + /* Disable EXTI global interrupt to avoid the EXTI to enter EXTI interrupt + while transmitting a frame */ + + if (lcd_cec_user_cb) extint_disable(OMV_CEC_PIN->pin); + + /* Send start bit */ + CEC_SendStartBit(); + + /* Send initiator and follower addresses. If the Header block is not + transmitted successfully then exit and return error */ + if (CEC_SendByte(HeaderBlockValueToSend) == false) + { + if (lcd_cec_user_cb) extint_enable(OMV_CEC_PIN->pin); + + /* Exit and return send failed */ + return false; + } + + /* Send data bytes */ + for (i = 0; i < MessageLength; i++) + { + if (i == (MessageLength - 1)) + { + cec_last_byte = 1; + } + + /* Send data byte and check if the follower sent the ACK bit = 0 */ + if (CEC_SendByte(Message[i]) == false) + { + if (lcd_cec_user_cb) extint_enable(OMV_CEC_PIN->pin); + + /* Exit and return send failed */ + return false; + } + } + + if (lcd_cec_user_cb) extint_enable(OMV_CEC_PIN->pin); + + /* Exit and return send succeeded */ + return true; +} + +static uint32_t CEC_ReceiveFrame(uint8_t* Message, uint8_t FollLogAdd) +{ + uint32_t i = 0; + uint32_t InitiatorAddress = 0; + uint32_t ReceiveStatus = 1; + uint32_t FrameSendToMe = 0; + cec_byte = 0; + cec_eom = 0; + + /* If the start bit has been received successfully */ + if (CEC_ReceiveStartBit()) + { + /* Get the first byte and check if the byte has been received correctly */ + if (CEC_ReceiveByte(HeaderBlock)) + { + /* Get the initiator address */ + InitiatorAddress = cec_byte >> 4; + + /* If the frame was sent to me */ + if ((cec_byte & 0x0F) == FollLogAdd) + { + CEC_SendAckBit(); /* Send Acknowledge bit = 0 */ + FrameSendToMe = 1; + + /* Continue to read the data byte since the frame is not completed */ + while (!cec_eom) + { + /* Check if the byte has been received correctly */ + if (CEC_ReceiveByte(DataBlock)) + { + /* Build the frame */ + Message[i] = cec_byte; + i++; + } + else /* If the byte has not been received correctly */ + { + /* Set receive status bit (Error) */ + ReceiveStatus = 0; + } + } + } + } + } + else /* If the start bit has not been received successfully */ + { + /* Set receive status bit (Error) */ + ReceiveStatus = 0; + } + + /* Return the different receive infos: Receive status, number of data byte received, + Frame send to me or not and the initiator address */ + return ((((InitiatorAddress) | (i << 8)) | (ReceiveStatus << 16)) | (FrameSendToMe << 17)); +} + +//////////////////////////////////////////////////////////////////////////////// + +static bool lcd_cec_receive_frame_int(mp_obj_t dst_addr, bool assertOnError) +{ + mp_obj_array_t *bytes = (mp_obj_array_t *) lcd_cec_bytes; + uint32_t result = CEC_ReceiveFrame(bytes->items, mp_obj_get_int(dst_addr)); + bool receiveFrameStatus = result & ReceiveFrameStatusMask; + bool frameSendToMe = result & FrameSendToMeMask; + + if (assertOnError && (!receiveFrameStatus)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Receive Failed!")); + } + + if (receiveFrameStatus && frameSendToMe) { + lcd_cec_src_addr = result & InitiatorAddressMask; + bytes->len = (result >> 8) & 0xFF; + return true; + } + + return false; +} + +void lcd_cec_send_frame(mp_obj_t dst_addr, mp_obj_t src_addr, mp_obj_t bytes) +{ + size_t len; + uint8_t *data = (uint8_t *) mp_obj_str_get_data(bytes, &len); + if (!CEC_SendFrame(mp_obj_get_int(src_addr), mp_obj_get_int(dst_addr), len, data)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Send Failed!")); + } +} + +mp_obj_t lcd_cec_received_frame_src_addr() +{ + return mp_obj_new_int(lcd_cec_src_addr); +} + +mp_obj_t lcd_cec_received_frame_bytes() +{ + return lcd_cec_bytes; +} + +mp_obj_t lcd_cec_receive_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (n_args > 0) { + int timeout = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_timeout), 1000); + + mp_uint_t start = mp_hal_ticks_ms(); + while (HAL_GPIO_ReadPin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask)) { + if ((mp_hal_ticks_ms() - start) > timeout) nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Receive timeout!")); + } + + return mp_obj_new_bool(lcd_cec_receive_frame_int(args[0], true)); + } + + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Expected destination address!")); +} + +STATIC mp_obj_t lcd_cec_extint_callback(mp_obj_t line) +{ + if (lcd_cec_user_cb && lcd_cec_receive_frame_int(lcd_cec_dst_addr, false)) mp_call_function_0(lcd_cec_user_cb); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(lcd_cec_extint_callback_obj, lcd_cec_extint_callback); + +void lcd_cec_deinit() +{ + extint_disable(OMV_CEC_PIN->pin); + + lcd_cec_user_cb = NULL; + lcd_cec_src_addr = 0; + lcd_cec_dst_addr = NULL; + lcd_cec_bytes = NULL; + + HAL_GPIO_DeInit(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask); +} + +void lcd_cec_init() +{ + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = OMV_CEC_PIN->pin_mask; + HAL_GPIO_Init(OMV_CEC_PIN->gpio, &GPIO_InitStructure); + + HAL_GPIO_WritePin(OMV_CEC_PIN->gpio, OMV_CEC_PIN->pin_mask, GPIO_PIN_SET); + HAL_Delay(1); + + lcd_cec_bytes = mp_obj_new_bytearray_by_ref(0, m_new(byte, 16)); + + extint_register((mp_obj_t) OMV_CEC_PIN, GPIO_MODE_IT_FALLING, GPIO_PULLUP, (mp_obj_t) &lcd_cec_extint_callback_obj, true); +} + +void lcd_cec_register_cec_receive_cb(mp_obj_t cb, mp_obj_t dst_addr) +{ + extint_disable(OMV_CEC_PIN->pin); + lcd_cec_user_cb = cb; + lcd_cec_dst_addr = dst_addr; + if (cb != mp_const_none) extint_enable(OMV_CEC_PIN->pin); +} +#endif // OMV_CEC_PRESENT diff --git a/src/omv/py/py_lcd_cec.h b/src/omv/py/py_lcd_cec.h new file mode 100644 index 000000000..7609b63b4 --- /dev/null +++ b/src/omv/py/py_lcd_cec.h @@ -0,0 +1,20 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * LCD Python module. + */ +#ifndef __PY_LCD_CEC_H__ +#define __PY_LCD_CEC_H__ +void lcd_cec_init(); +void lcd_cec_deinit(); +void lcd_cec_send_frame(mp_obj_t dst_addr, mp_obj_t src_addr, mp_obj_t bytes); +mp_obj_t lcd_cec_receive_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args); +void lcd_cec_register_cec_receive_cb(mp_obj_t cb, mp_obj_t dst_addr); +mp_obj_t lcd_cec_received_frame_src_addr(); +mp_obj_t lcd_cec_received_frame_bytes(); +#endif // __PY_LCD_CEC_H__ diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 649bf90dc..9296f98b4 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -1198,6 +1198,11 @@ Q(set_backlight) Q(get_backlight) Q(get_display_connected) Q(get_display_id_data) +Q(send_frame) +Q(receive_frame) +Q(register_receive_cb) +Q(received_frame_src_addr) +Q(received_frame_bytes) Q(register_hotplug_cb) Q(update_touch_points) Q(register_touch_cb)