Ashok R
Published

Rapid IoT Kit - Wireless UART

This tutorial demonstrate the "Wireless UART" functionality on NXP's Rapid IoT prototyping kit.

BeginnerFull instructions provided6 hours991
Rapid IoT Kit - Wireless UART

Things used in this project

Hardware components

Rapid IoT Prototyping Kit
NXP Rapid IoT Prototyping Kit
×1

Software apps and online services

NXP IoT ToolBox

Story

Read more

Schematics

RPK Schematic Blocks

No changes in schematic

Code

hello world

C/C++
/*
 * Copyright (c) 2014 - 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*==================================================================================================
 Include Files
 ==================================================================================================*/
#include <stdio.h>
#include "fsl_device_registers.h"
#include "fsl_os_abstraction.h"
#include "fsl_sysmpu.h"
#include "fsl_port.h"
#include "pin_mux.h"

#if WDOG_ENABLE
    #include "fsl_wdog.h"
#endif

#ifndef FRDM_K64F_KW41Z
    #include "PhyInterface.h"
    #include "MacInterface.h"
#endif

/* FSL Framework */
#include "LED.h"
#include "rgb_led.h"
#include "backlight.h"
#include "sensors.h"
#include "nvm_adapter.h"
#include "TimersManager.h"
#include "Keyboard.h"
#include "SerialManager.h"
#include "Panic.h"

#include "fsl_os_abstraction.h"
#include "app_init.h"
#include "app_config.h"
#include "thread_network.h"
#include "thread_cfg.h"
#include "debug_log.h"

#if THREAD_USE_THCI
    #include "thci.h"
    #include "FsciInterface.h"
#endif

#if THREAD_USE_SHELL
    #include "shell_ip.h"
#endif

#ifdef FRDM_K64F_KW41Z
    #include "FsciInterface.h"
    #include "cmd_threadip.h"
    #include "shell_ip.h"

    #if gHybridApp_d
        #include "cmd_ble.h"
        #include "ble_shell.h"
    #endif

    #if SHELL_DEBUGGER
        #include "shell_peripherals.h"
    #endif

    #if LWIP_IPV6
        #include "lwip/opt.h"
        #include "lwip/tcpip.h"
        #include "lwip/ethip6.h"
        #include "lwip/dhcp.h"
        #include "lwip/mld6.h"
        #include "lwip/prot/dhcp.h"
        #include "netif/ethernet.h"
        #include "ethernetif.h"
    #endif
#endif

/* Display */
#include "BUTTON.h"
#include "CHECKBOX.h"
#include "DROPDOWN.h"
#include "GUI.h"
#include "MULTIPAGE.h"
#include "RADIO.h"
#include "SLIDER.h"
#include "emwin_support.h"

extern void APP_Init(void);
extern void APP_Handler(void);

#if gLpmIncluded_d
    extern void App_SedWakeUpFromKeyBoard(void);
#endif

#if THCI_USB_ENABLE && THR_SOFTWARE_RESET_ENABLE
    extern void THCI_ResetCpuEvent(resetCpuStatus_t resetStatus, uint32_t timeoutUs);
#endif

/*==================================================================================================
 Private macros
 ==================================================================================================*/
#define gFSCI_IpStackOpGCnf_c                   0xCFU
/*! FSCI utility Confirmations/Indications              */
#define gFSCI_CnfOpcodeGroup_c                  0xA4
/*! FSCI operation group for GATT Database (application) */
#define gFsciBleL2capOpcodeGroup_c              0x41
/*! FSCI operation group for GATT */
#define gFsciBleGattOpcodeGroup_c               0x44
/*! FSCI operation group for GATT Database (application) */
#define gFsciBleGattDbAppOpcodeGroup_c          0x45
/*! FSCI operation group for GAP */
#define gFsciBleGapOpcodeGroup_c                0x47

/* ATTENTION: the following static configuration is not used, as DHCP is used instead */
/* IP address configuration. */
#define configIP_ADDR0 192
#define configIP_ADDR1 168
#define configIP_ADDR2 0
#define configIP_ADDR3 142

/* Netmask configuration. */
#define configNET_MASK0 255
#define configNET_MASK1 255
#define configNET_MASK2 255
#define configNET_MASK3 0

/* Gateway address configuration. */
#define configGW_ADDR0 192
#define configGW_ADDR1 168
#define configGW_ADDR2 0
#define configGW_ADDR3 1

/*==================================================================================================
 Private type definitions
 ==================================================================================================*/

/*==================================================================================================
 Private prototypes
 ==================================================================================================*/
static void fsciThciRegister(uint32_t fsciInterfaceId);
static void fsciBleRegister(uint32_t fsciInterfaceId);
static void THCI_RxCb(void *pData, void *param, uint32_t interfaceId);
static void BLE_FSCI_RxCb(void *pData, void *param, uint32_t interfaceId);

static void APP_HandleMcuResetOnIdle(void);
#if gLpmIncluded_d
    static void APP_HandleLowPowerOnIdle(void);
#endif
#if WDOG_ENABLE
    static void APP_WDOG_Init(void);
    static void APP_WDOG_Refresh(void);
#endif
/*==================================================================================================
 Private global variables declarations
 ==================================================================================================*/

void (*pfAppKeyboardHandler)(void *) = NULL;

#if WDOG_ENABLE
/* Configure watchdog. */
const wdog_config_t wdogConfig =
{
    .enableWdog = TRUE, /* Watchdog mode */
    .timeoutValue = 0x4096U, /* Watchdog overflow time is about 4s*/
    .enableWindowMode = FALSE, /* Disable window function */
    .windowValue = 0, /* Watchdog window value */
    .prescaler = kWDOG_ClockPrescalerDivide1, /* Watchdog clock prescaler */
    .enableUpdate = TRUE, /* Update register enabled */
    .clockSource = kWDOG_LpoClockSource, /* Watchdog clock source is LPO 1KHz */
#if defined(FSL_FEATURE_WDOG_HAS_WAITEN) && FSL_FEATURE_WDOG_HAS_WAITEN
    .workMode.enableWait = TRUE, /* Enable watchdog in wait mode */
#endif
    .workMode.enableStop = FALSE, /* Enable watchdog in stop mode */
    .workMode.enableDebug = FALSE, /* Disable watchdog in debug mode */
};

static WDOG_Type *wdog_base = WDOG;
#endif

/*!< reset MCU timestamp <microseconds> */
static uint64_t gSwResetTimestamp = 0;

/*!< boolean -  nvm format */
static bool_t gResetToFactory = FALSE;

/* FSCI Interface Configuration structure */
static const gFsciSerialConfig_t mFsciSerials[] =
{
    {
        .baudrate = gUARTBaudRate115200_c,
        .interfaceType = gSerialMgrUart_c,
        .interfaceChannel = 4,
        .virtualInterface = 0

    },
#if gHybridApp_d
    {
        .baudrate = gUARTBaudRate115200_c,
        .interfaceType = gSerialMgrUart_c,
        .interfaceChannel = 4,
        .virtualInterface = 1

    }
#endif
};

#if 0
static struct netif fsl_netif0;
/* IPv6 multicast group FF03::3EAD */
static const ip_addr_t realmlocal_mcast_3ead =
    IPADDR6_INIT(PP_HTONL(0xFF030000UL),
                 PP_HTONL(0x00000000UL),
                 PP_HTONL(0x00000000UL),
                 PP_HTONL(0x00003EADUL)
                );
#endif

/*==================================================================================================
 Public global variables declarations
 ==================================================================================================*/
taskMsgQueue_t appThreadMsgQueue;
osaSemaphoreId_t gOtaSem;
char lcdtext[32];
char *lcdp = lcdtext;
char linecount= 9;
int wuartreceived = 0;
/*==================================================================================================
 Public functions
 ==================================================================================================*/
/*==================================================================================================
 ==================================================================================================*/

/* Functions required for emwin / Display */
uint32_t DSPI2_GetFreq(void)
{
    return CLOCK_GetBusClkFreq();
}

void Clear_Buffer(void){

	int i=0;

	for(i=0;i<32;i++){

		lcdtext[i]='\0';
	}
}

void Init_Display()
{
    Display_Connect(); /* triggers GUI_Init() */
    Backlight_SetLevel(BLIGHT_LEVEL_HIGH);

    GUI_SetBkColor(GUI_BLACK);
    GUI_SetColor(GUI_WHITE);
}

#define GUI_FONT_TITLE  GUI_Font8x18
#define GUI_FONT_NORMAL GUI_Font8x16

void main_task(uint32_t param)
{
    static uint8_t mainInitialized = FALSE;

    if (!mainInitialized)
    {
        mainInitialized = TRUE;

#if WDOG_ENABLE
        /* Init watchdog module */
        APP_WDOG_Init();
#endif
        /* Init memory blocks manager */
        MEM_Init();

        /* Init  timers module */
        TMR_Init();
        TMR_TimeStampInit();

        SerialManager_Init();

#ifdef FRDM_K64F_KW41Z
        /* Initialize shell for Thread commands */
        SHELLComm_Init(&appThreadMsgQueue);
        /* Initialize FSCI (on two virtual interfaces if hybrid mode is on) */
        FSCI_Init((void *)&mFsciSerials);
#if gHybridApp_d
        /* Initialize shell for BLE commands */
        BleApp_Init();
#endif

#if SHELL_DEBUGGER
        /* Initialize shell get and set commands*/
        Cmd_Init();
#endif
#endif
    }

    /* Register Handler for events coming from KW41Z on interface 0 */
    fsciThciRegister(0);

#if gHybridApp_d
    /* Register Handler for BLE events coming from KW41Z on virtual interface 1 */
    fsciBleRegister(1);
#endif

    /* Create semaphore to disable (lock) UI manager while running OTA */
    gOtaSem = OSA_SemaphoreCreate(0U);

    if (NULL == gOtaSem)
    {
        panic(0,0,0,0);
    }

    /* Initialize all sensors */
    Init_all_sensors();

    /* Init Led module */
    LED_Init();

    /* Turn on LCD display */
    Init_Display();
    /* Display a Message */
      GUI_SetFont(&GUI_Font8x18);
      GUI_SetBkColor(GUI_WHITE);
      GUI_Clear();
      GUI_SetColor(GUI_BLUE);
      GUI_DispString("\n Rapid IoT\n\n");
      GUI_DispString(" WUART Demo!\n\n");
    /* Write to the terminal through the shell */
    shell_write("\r\n\n** Running Rapid IoT - WUART Demo! **\n\n\r");

    /* Main Application Loop (idle state) */
    while (1)
    {

    	if(wuartreceived){

    		shell_write("\n");
    		shell_write(lcdp);

    		if(linecount>8)
    		{
				GUI_Clear();
				GUI_DispString("\n");
				linecount=0;
    		}
    		GUI_DispString(" ");
    		GUI_DispString(lcdp);
    		wuartreceived = 0;
    		linecount++;
    		Clear_Buffer();

    	}

        /* Debug Checks, Leader LED restore check */
        DBG_Check();

        /* Reset MCU */
        APP_HandleMcuResetOnIdle();

        /* For BareMetal break the while(1) after 1 run */
        if (gUseRtos_c == 0)
        {
            break;
        }
    }
}

bool_t SERIAL_TAP_IP6_SEND(struct pbuf *p, struct netif *inp)
{
    struct ip6_hdr *ip6hdr = (struct ip6_hdr *)p->payload;

    SerialTun_IPPacketSendRequest_t req =
    {
        .Size = SIZEOF_ETH_HDR + p->tot_len,
        .Data = (uint8_t *)p->payload - SIZEOF_ETH_HDR
    };

    // TCP traffic is not sent to the black-box
    if (IP6H_NEXTH(ip6hdr) != IP6_NEXTH_TCP)
    {
        SerialTun_IPPacketSendRequest(&req, 0);

        // UDP traffic is not processed by LwIP
        if (IP6H_NEXTH(ip6hdr) == IP6_NEXTH_UDP)
        {
            pbuf_free(p);
            return TRUE;
        }

    }

    // not eaten traffic to be processed by LwIP
    return FALSE;
}

/*!*************************************************************************************************
 \fn     APP_ResetMcuOnTimeout
 \brief  Reset the MCU on timeout
 \param  [in]    timeoutMs  timeout in milliseconds
 \param  [in]    resetToFactory
 \return         None
 ***************************************************************************************************/
void APP_ResetMcuOnTimeout(uint32_t timeoutMs, bool_t resetToFactory)
{
    gResetToFactory = resetToFactory;
    gSwResetTimestamp = TMR_GetTimestamp();
    gSwResetTimestamp += (timeoutMs * 1000); /* microseconds*/
}

/*!*************************************************************************************************
 \fn     APP_GetResetMcuTimeout
 \brief  Return the interval time until a MCU reset occurs
 \return  the time interval; 0 means that no Mcu reset was programmed
 ***************************************************************************************************/
uint32_t APP_GetResetMcuTimeout(void)
{
    uint32_t timeInterval = 0;

    if (gSwResetTimestamp > TMR_GetTimestamp())
    {
        timeInterval = (uint32_t)((gSwResetTimestamp - TMR_GetTimestamp())
                                  / 1000);
    }

    return timeInterval;
}

/*==================================================================================================
 Private functions
 ==================================================================================================*/
static void fsciThciRegister(uint32_t fsciInterfaceId)
{
    if (FSCI_RegisterOpGroup(gFSCI_IpStackOpGCnf_c,
                             gFsciMonitorMode_c,
                             THCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciThciRegister, 0, 0);
    }
}

static void fsciBleRegister(uint32_t fsciInterfaceId)
{
    /* Register Generic FSCI */
    if (FSCI_RegisterOpGroup(gFSCI_CnfOpcodeGroup_c,
                             gFsciMonitorMode_c,
                             BLE_FSCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciBleRegister, 0, 0);
    }

    /* Register L2CAP command handler */
    if (FSCI_RegisterOpGroup(gFsciBleL2capOpcodeGroup_c,
                             gFsciMonitorMode_c,
                             BLE_FSCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciBleRegister, 0, 0);
    }

    /* Register GATT command handler */
    if (FSCI_RegisterOpGroup(gFsciBleGattOpcodeGroup_c,
                             gFsciMonitorMode_c,
                             BLE_FSCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciBleRegister, 0, 0);
    }

    /* Register GATT Database (application) command handler */
    if (FSCI_RegisterOpGroup(gFsciBleGattDbAppOpcodeGroup_c,
                             gFsciMonitorMode_c,
                             BLE_FSCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciBleRegister, 0, 0);
    }

    /* Register GAP command handler */
    if (FSCI_RegisterOpGroup(gFsciBleGapOpcodeGroup_c,
                             gFsciMonitorMode_c,
                             BLE_FSCI_RxCb,
                             NULL,
                             fsciInterfaceId) != gFsciSuccess_c)
    {
        panic(0, (uint32_t)fsciBleRegister, 0, 0);
    }
}

static void THCI_RxCb
(
    void *pData,
    void *param,
    uint32_t interfaceId
)
{
    thrEvtContainer_t container; // this could be allocated instead
    KHC_ThreadIP_RX_MsgHandler(pData, &container, interfaceId);

    SHELL_ThrEventNotify(&container);
}

static void BLE_FSCI_RxCb
(
    void *pData,
    void *param,
    uint32_t interfaceId
)
{
#if gHybridApp_d
    bleEvtContainer_t container; // this could be allocated instead
    KHC_BLE_RX_MsgHandler(pData, &container, interfaceId);
    SHELL_BleEventNotify(&container);
#endif
}

/*!*************************************************************************************************
 \fn     APP_HandleMcuResetOnIdle
 \brief  Reset the MCU on idle
 \param  [in]
 \return         None
 ***************************************************************************************************/
static void APP_HandleMcuResetOnIdle(void)
{
    if ((gSwResetTimestamp) && (gSwResetTimestamp < TMR_GetTimestamp()))
    {
        gSwResetTimestamp = 0;
        /* disable interrupts */
        OSA_InterruptDisable();

#if THCI_USB_ENABLE && THR_SOFTWARE_RESET_ENABLE
        THR_SoftwareReset(0, gResetToFactory);
        /* inform application */
        THCI_ResetCpuEvent(gResetCpuSuccess_c, 0);
#else

        if (gResetToFactory)
        {
            /* Erase NVM Datasets */
            //NvFormat();
        }

        ResetMCU();
#endif
        /* Enable interrupts */
        OSA_InterruptEnable();
    }
}

/*!*************************************************************************************************
 \fn     APP_HandleLowPowerOnIdle
 \brief  Handle low power on idle
 \param  [in]
 \return         None
 ***************************************************************************************************/
#if gLpmIncluded_d
static void APP_HandleLowPowerOnIdle(void)
{
    if (PWR_CheckIfDeviceCanGoToSleep())
    {
        PWRLib_WakeupReason_t wakeupReason;
        wakeupReason = PWR_EnterLowPower();

        if (wakeupReason.Bits.FromKeyBoard)
        {
            /* Protection to the LLWD pin enabled on both edges */
            static bool_t wakeUpFlag = FALSE;

            if (TRUE == wakeUpFlag)
            {
                wakeUpFlag = FALSE;
                App_SedWakeUpFromKeyBoard();
            }
            else
            {
                wakeUpFlag = TRUE;
            }

            PWR_AllowDeviceToSleep();
        }
    }
}
#endif

/*!*************************************************************************************************
 \fn     static void APP_WDOG_Init(void)
 \brief  Init watch dog if enabled
 ***************************************************************************************************/
#if WDOG_ENABLE
static void APP_WDOG_Init(void)
{

    uint32_t i = 0;

    WDOG_Init(wdog_base, &wdogConfig);

    /* Accessing register by bus clock */
    for (i = 0; i < 256; i++)
    {
        (void)WDOG->RSTCNT;
    }
}

/*!*************************************************************************************************
 \fn     static void APP_WDOG_Refresh(void)
 \brief  Refresh watch dog if enabled
 ***************************************************************************************************/

static void APP_WDOG_Refresh(void)
{
    uint32_t wdogTimer = (uint32_t)((((uint32_t)wdog_base->TMROUTH) << 16U) | (wdog_base->TMROUTL));

    /* Restart the watchdog so it doesn't reset */
    if (wdogTimer > (wdogConfig.timeoutValue >> 3U))
    {
        WDOG_Refresh(wdog_base);
    }
}
#endif
/*==================================================================================================
 Private debug functions
 ==================================================================================================*/

FSCI Communication

C/C++
/*!
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2017 NXP
*
* \file
*
* This is a source file for the FSCI communication.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice, this list
*   of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice, this
*   list of conditions and the following disclaimer in the documentation and/or
*   other materials provided with the distribution.
*
* o Neither the name of Freescale Semiconductor, Inc. nor the names of its
*   contributors may be used to endorse or promote products derived from this
*   software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/************************************************************************************
*************************************************************************************
* Include
*************************************************************************************
************************************************************************************/

#include "FsciCommunication.h"
#include "FsciInterface.h"
#include "FsciCommands.h"
#include "FunctionLib.h"

#include "MemManager.h"
#include "Panic.h"
#include "TimersManager.h"

#include "fsl_os_abstraction.h"
#include "thci.h"

#if gNvStorageIncluded_d
#include "NVM_Interface.h"
#endif

#if gFsciIncluded_c
/************************************************************************************
*************************************************************************************
* Private macros
*************************************************************************************
************************************************************************************/
#define gFsciUseBlockingTx_c 1
#define MIN_VALID_PACKET_LEN (sizeof(clientPacketHdr_t))
#define FSCI_txCallback MEM_BufferFree
#define FSCI_rxCallback FSCI_receivePacket

#ifndef mFsciRxAckTimeoutMs_c
#define mFsciRxAckTimeoutMs_c 100 /* milliseconds */
#endif

#ifndef mFsciTxRetryCnt_c
#define mFsciTxRetryCnt_c     4
#endif

#ifndef mFsciRxRestartTimeoutMs_c
#define mFsciRxRestartTimeoutMs_c 50 /* milliseconds */
#endif

/************************************************************************************
*************************************************************************************
* Private prototypes
*************************************************************************************
************************************************************************************/
fsci_packetStatus_t FSCI_checkPacket( clientPacket_t *pData, uint16_t bytes, uint8_t* pVIntf );

#if gFsciRxAck_c && gFsciRxAckTimeoutUseTmr_c
static void FSCI_RxAckExpireCb(void *param);
#endif

#if gFsciRxTimeout_c && !mFsciRxTimeoutUsePolling_c
static void FSCI_RxRxTimeoutCb(void *param);
#endif

static void FSCI_SendPacketToSerialManager(uint32_t fsciInterface, uint8_t *pPacket, uint16_t packetLen);

/************************************************************************************
*************************************************************************************
* Private type definitions
*************************************************************************************
************************************************************************************/

/************************************************************************************
*************************************************************************************
* Public memory declarations
*************************************************************************************
************************************************************************************/
uint8_t gFsciTxBlocking = FALSE;
uint8_t gFsciTxDisable  = FALSE;

/* Holds the SMGR interface Id associated with the FSCI interface (index) */
uint8_t gFsciSerialInterfaces[gFsciMaxInterfaces_c];

extern char lcdtext[32];
extern int wuartreceived;

#if gFsciMaxVirtualInterfaces_c
/* Holds the virtual interface Id for the FSCI interface (index) */
uint8_t gFsciVirtualInterfaces[gFsciMaxInterfaces_c];
#endif

#if gFsciHostSupport_c
    #if gFsciHostSyncUseEvent_c && !USE_RTOS
    #undef gFsciHostSyncUseEvent_c
    #endif

    #if gFsciHostSyncUseEvent_c
    osaEventId_t gFsciHostSyncRspEventId;
    #endif

clientPacket_t *pFsciHostSyncRsp = NULL;
bool_t          gFsciHostWaitingSyncRsp = FALSE;
opGroup_t       gFsciHostWaitingOpGroup = 0;
opCode_t        gFsciHostWaitingOpCode = 0;
#endif

/************************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
************************************************************************************/
static fsciComm_t mFsciCommData[gFsciMaxInterfaces_c];

static uint8_t mFsciSrcInterface = mFsciInvalidInterface_c;

/************************************************************************************
*************************************************************************************
* Public functions
*************************************************************************************
************************************************************************************/

#if gFsciHostSupport_c
void FSCI_HostSyncLock(uint32_t fsciInstance, opGroup_t OG, opCode_t OC)
{
    OSA_MutexLock(mFsciCommData[fsciInstance].syncHostMutexId, osaWaitForever_c);
    gFsciHostWaitingSyncRsp = TRUE;
    gFsciHostWaitingOpGroup = OG;
    gFsciHostWaitingOpCode = OC;
    pFsciHostSyncRsp = NULL;
}

void FSCI_HostSyncUnlock(uint32_t fsciInstance)
{
    gFsciHostWaitingSyncRsp = FALSE;
    pFsciHostSyncRsp = NULL;
    OSA_MutexUnlock(mFsciCommData[fsciInstance].syncHostMutexId);
}
#endif

/*! *********************************************************************************
* \brief  Initialize the serial interface.
*
* \param[in]  initStruct pointer to a gFsciSerialConfig_t structure
*
********************************************************************************** */
void FSCI_commInit( gFsciSerialConfig_t* pSerCfg )
{
    uint32_t i;
#if gFsciMaxVirtualInterfaces_c
    uint32_t j;
#endif

    if (NULL == pSerCfg)
    {
        panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
        return;
    }

    FLib_MemSet( mFsciCommData, 0x00, sizeof(mFsciCommData) );

#if gFsciHostSupport_c && gFsciHostSyncUseEvent_c
    if( (gFsciHostSyncRspEventId = OSA_EventCreate(TRUE)) == NULL )
    {
        panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
        return;
    }
#endif
#if gFsciHostSupport_c && !gFsciHostSyncUseEvent_c
    TMR_TimeStampInit();
#endif
    for ( i = 0; i < gFsciMaxInterfaces_c; i++ )
    {
        gFsciSerialInterfaces[i] = gSerialMgrInvalidIdx_c;

#if gFsciMaxVirtualInterfaces_c
        gFsciVirtualInterfaces[i] = pSerCfg[i].virtualInterface;

        if( pSerCfg[i].virtualInterface >= gFsciMaxInterfaces_c )
        {
            panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
            break;
        }

        /* Check if the serial interface was allready initialized */
        for( j = 0; j < i; j++ )
        {
            if( (pSerCfg[i].interfaceType == pSerCfg[j].interfaceType) &&
                (pSerCfg[i].interfaceChannel == pSerCfg[j].interfaceChannel) )
            {
                gFsciSerialInterfaces[i] = gFsciSerialInterfaces[j];
            }
        }

        if( gFsciSerialInterfaces[i] == gSerialMgrInvalidIdx_c )
#endif
        {
            Serial_InitInterface(&gFsciSerialInterfaces[i],
                                 pSerCfg[i].interfaceType,
                                 pSerCfg[i].interfaceChannel );

            Serial_SetRxCallBack( gFsciSerialInterfaces[i], FSCI_rxCallback, (void*)i);
            Serial_SetBaudRate  ( gFsciSerialInterfaces[i], pSerCfg[i].baudrate );

#if gFsciHostSupport_c
            if( (mFsciCommData[i].syncHostMutexId = OSA_MutexCreate()) == NULL )
            {
                panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
                break;
            }
#endif

#if gFsciRxAck_c
            if( (mFsciCommData[i].syncTxRxAckMutexId = OSA_MutexCreate()) == NULL )
            {
                panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
                break;
            }

#if gFsciRxAckTimeoutUseTmr_c
            mFsciCommData[i].ackWaitTmr = TMR_AllocateTimer();
            if( gTmrInvalidTimerID_c == mFsciCommData[i].ackWaitTmr )
            {
                panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
                break;
            }
#else
            TMR_TimeStampInit();
#endif

            mFsciCommData[i].txRetryCnt = mFsciTxRetryCnt_c;
            mFsciCommData[i].ackReceived = FALSE;
            mFsciCommData[i].ackWaitOngoing = FALSE;
#endif

#if gFsciRxTimeout_c
#if mFsciRxTimeoutUsePolling_c
            TMR_TimeStampInit();
#else
            mFsciCommData[i].rxRestartTmr = TMR_AllocateTimer();
            if( gTmrInvalidTimerID_c == mFsciCommData[i].rxRestartTmr )
            {
                panic( ID_PANIC(0,0), (uint32_t)FSCI_commInit, 0, 0 );
                break;
            }
#endif

            mFsciCommData[i].rxOngoing = FALSE;
            mFsciCommData[i].rxTmrExpired = FALSE;
#endif
        }
    }
}

/*! *********************************************************************************
* \brief  Receives data from the serial interface and checks to see if we have a valid pachet.
*
* \param[in]  param the fsciInterface on which the data has been received
*
********************************************************************************** */
void FSCI_receivePacket( void* param )
{

    uint16_t            readBytes;
    uint8_t             c;
    static uint8_t bytesReceived=0;

    if( gSerial_Success_c != Serial_GetByteFromRxBuffer( gFsciSerialInterfaces[(uint32_t)param], &c, &readBytes ) )
    {
        return;
    }

    while( readBytes && !wuartreceived)
    {

        if(c == '\r'){

        	bytesReceived = 0;
			wuartreceived = 1;
        }
        else
        {

            lcdtext[bytesReceived++] =c;

        }  /* if (!startOfFrameSeen) */

        if ( gSerial_Success_c != Serial_GetByteFromRxBuffer( gFsciSerialInterfaces[(uint32_t)param], &c, &readBytes ) )
        {
            break;
        }

    }


}


/*! *********************************************************************************
* \brief  Send packet over the serial interface, after computing Checksum.
*
* \param[in] pPacket pointer to the packet to be sent over the serial interface
* \param[in] fsciInterface the interface on which the packet should be sent
*
********************************************************************************** */
void FSCI_transmitFormatedPacket( void *pPacket, uint32_t fsciInterface )
{
    clientPacket_t *pPkt = pPacket;
    uint32_t        size;
    uint32_t        virtInterface = FSCI_GetVirtualInterface(fsciInterface);
    uint8_t         checksum;

    pPkt->structured.header.startMarker = gFSCI_StartMarker_c;
    size = sizeof(clientPacketHdr_t) + pPkt->structured.header.len + 1 /* CRC */;

    /* Compute Checksum */
    checksum = FSCI_computeChecksum( pPkt->raw+1, size - 2);

    pPkt->structured.payload[pPkt->structured.header.len] = checksum;

    if( virtInterface )
    {
#if gFsciMaxVirtualInterfaces_c
        pPkt->structured.payload[pPkt->structured.header.len] += virtInterface;
        pPkt->structured.payload[pPkt->structured.header.len+1] = checksum^(checksum + virtInterface);
        size += sizeof(checksum);
#else
       (void)virtInterface;
#endif
    }

    /* send message to Serial Manager */
    FSCI_SendPacketToSerialManager(fsciInterface, pPkt->raw, size);
}

/*! *********************************************************************************
* \brief  Encode and send messages over the serial interface
*
* \param[in] OG operation Group
* \param[in] OC operation Code
* \param[in] pMsg pointer to payload
* \param[in] msgLen length of the payload
* \param[in] fsciInterface the interface on which the packet should be sent
*
********************************************************************************** */
void FSCI_transmitPayload( uint8_t OG, uint8_t OC, void *pMsg, uint16_t msgLen, uint32_t fsciInterface )
{
    uint8_t* buffer_ptr = NULL;
    uint16_t buffer_size, index;
    uint8_t checksum, checksum2;
    clientPacketHdr_t header;
    uint32_t virtInterface = FSCI_GetVirtualInterface(fsciInterface);

    if( gFsciTxDisable || (msgLen > gFsciMaxPayloadLen_c) )
    {
        return;
    }

    /* Compute size */
    buffer_size = sizeof(clientPacketHdr_t) + msgLen + 2*sizeof(checksum);

#if gFsciUseEscapeSeq_c
    buffer_size = buffer_size*2;
#endif

    /* Allocate buffer */
    buffer_ptr = MEM_BufferAlloc( buffer_size );
    if( NULL == buffer_ptr )
    {
        return;
    }

    /* Message header */
    header.startMarker = gFSCI_StartMarker_c;
    header.opGroup = OG;
    header.opCode = OC;
    header.len = msgLen;

    /* Compute CRC for TX packet, on opcode group, opcode, payload length, and payload fields */
    checksum = FSCI_computeChecksum((uint8_t*)&header + 1, sizeof(header) - 1);
    checksum ^= FSCI_computeChecksum((uint8_t*)pMsg, msgLen);
    if( virtInterface )
    {
        checksum2 = checksum^(checksum + virtInterface);
        checksum += virtInterface;
    }

    index = 0;
#if gFsciUseEscapeSeq_c
    index += FSCI_encodeEscapeSeq( (uint8_t*)&header, sizeof(header), &buffer_ptr[index] );
    index += FSCI_encodeEscapeSeq( pMsg, msgLen, &buffer_ptr[index]);
    /* Store the Checksum*/
    index += FSCI_encodeEscapeSeq( (uint8_t*)&checksum, sizeof(checksum), &buffer_ptr[index] );
    if( virtInterface )
    {
        index += FSCI_encodeEscapeSeq( (uint8_t*)&checksum2, sizeof(checksum2), &buffer_ptr[index] );
    }
    buffer_ptr[index++] = gFSCI_EndMarker_c;

#else /* gFsciUseEscapeSeq_c */
    FLib_MemCpy( &buffer_ptr[index], &header, sizeof(header) );
    index += sizeof(header);
    FLib_MemCpy( &buffer_ptr[index], pMsg, msgLen );
    index += msgLen;
    /* Store the Checksum */
    buffer_ptr[index++] = checksum;
    if( virtInterface )
    {
        buffer_ptr[index++] = checksum2;
    }

#endif /* gFsciUseEscapeSeq_c */

    /* send message to Serial Manager */
    FSCI_SendPacketToSerialManager(fsciInterface, buffer_ptr, index);
}

/*! *********************************************************************************
* \brief  Get a FSCI formatted packet from a payload message
*
* \param[in] OG operation Group
* \param[in] OC operation Code
* \param[in] pMsg pointer to payload
* \param[in] msgLen length of the payload
* \param[out] pOutLen the actual length of the formatted packet
*
********************************************************************************** */
uint8_t* FSCI_GetFormattedPacket(uint8_t OG, uint8_t OC, void *pMsg, uint16_t msgLen, uint16_t *pOutLen)
{
    uint8_t* pBuff = NULL;
    clientPacketHdr_t header;
    uint16_t index = 0;
    uint8_t checksum = 0;

    if( (msgLen > gFsciMaxPayloadLen_c) || (NULL == pOutLen) ||
        (msgLen && (NULL == pMsg)) )
    {
        return NULL;
    }
    else
    {
        pBuff = MEM_BufferAlloc(sizeof(header) + msgLen + sizeof(checksum));
        if( pBuff )
        {
            header.startMarker = gFSCI_StartMarker_c;
            header.opGroup = OG;
            header.opCode = OC;
            header.len = msgLen;

            FLib_MemCpy(pBuff, &header, sizeof(header));
            index += sizeof(header);
            FLib_MemCpy(pBuff+index, pMsg, msgLen);
            index += msgLen;
            checksum = FSCI_computeChecksum(pBuff+1, sizeof(header)-1 + msgLen);
            pBuff[index++] = checksum;

            *pOutLen = index;

            return pBuff;
        }
        else
        {
            return NULL;
        }
    }
}

/************************************************************************************
*************************************************************************************
* Private functions
*************************************************************************************
************************************************************************************/

/*! *********************************************************************************
* \brief  Returnd the virtual interface associated with the specified fsciInterface.
*
* \param[in] fsciInterface the interface on which the packet should be sent
*
* \return the Virtual Interface Id
*
********************************************************************************** */
uint32_t FSCI_GetVirtualInterface(uint32_t fsciInterface)
{
#if gFsciMaxVirtualInterfaces_c
    return gFsciVirtualInterfaces[fsciInterface];
#else
    return 0;
#endif
}

/*! *********************************************************************************
* \brief  Determines the FSCI interface for a received packet.
*
* \param[in] hwInterface the serial interface on which the packet was received.
* \param[in] virtualInterface the virtual interface of the received packet
*
* \return the FSCI interface
*
********************************************************************************** */
uint32_t FSCI_GetFsciInterface(uint32_t hwInterface, uint32_t virtualInterface)
{
#if gFsciMaxVirtualInterfaces_c
    uint32_t i;

    for( i = 0; i < gFsciMaxInterfaces_c; i++)
    {
        if( (virtualInterface == gFsciVirtualInterfaces[i]) && (hwInterface == gFsciSerialInterfaces[i]) )
        {
            hwInterface = i;
            break;
        }
    }
#endif
    return hwInterface;
}

/*! *********************************************************************************
* \brief  Checks to see if we have a valid packet
*
* \param[in] pData The message containing the incoming data packet to be handled.
* \param[in] bytes the number of bytes inside the buffer
* \param[Out] pVIntf pointer to the location where the virtual interface Id will be stored
*
* \return the status of the packet
*
********************************************************************************** */
fsci_packetStatus_t FSCI_checkPacket( clientPacket_t *pData, uint16_t bytes, uint8_t* pVIntf )
{
    uint8_t checksum = 0;
    uint16_t len;

    if ( bytes < MIN_VALID_PACKET_LEN )
    {
        return PACKET_IS_TO_SHORT;            /* Too short to be valid. */
    }

    if ( bytes >= sizeof(clientPacket_t) )
    {
        printf("framing_error1!\r\n");
        return FRAMING_ERROR;
    }

    if ( NULL == pData )
    {
        return INTERNAL_ERROR;
    }

    /* The packet's len field does not count the STX, the opcode group, the */
    /* opcode, the len field, or the checksum. */
    len = pData->structured.header.len;

    /* If the length appears to be too long, it might be because the external */
    /* client is sending a packet that is too long, or it might be that we're */
    /* out of sync with the external client. Assume we're out of sync. */
    if ( len > gFsciMaxPayloadLen_c )
    {
        printf("framing_error2!\r\n");
        return FRAMING_ERROR;
    }

    if ( bytes < len + sizeof(clientPacketHdr_t) + sizeof(checksum) )
    {
        return PACKET_IS_TO_SHORT;
    }

    /* If the length looks right, make sure that the checksum is correct. */
    if( bytes >= len + sizeof(clientPacketHdr_t) + sizeof(checksum) )
    {
        checksum = FSCI_computeChecksum(pData->raw+1, len + sizeof(clientPacketHdr_t)-1);
        *pVIntf = pData->structured.payload[len] - checksum;
    }

    if( bytes == len + sizeof(clientPacketHdr_t) + sizeof(checksum) )
    {
        if( 0 == *pVIntf )
        {
            return PACKET_IS_VALID;
        }
#if gFsciMaxVirtualInterfaces_c
        else
        {
            if( *pVIntf < gFsciMaxVirtualInterfaces_c )
            {
                return PACKET_IS_TO_SHORT;
            }
        }
#endif
    }

#if gFsciMaxVirtualInterfaces_c
    /* Check virtual interface */
    if( bytes == len + sizeof(clientPacketHdr_t) + 2*sizeof(checksum) )
    {
        checksum ^= checksum + *pVIntf;
        if( pData->structured.payload[len+1] == checksum )
        {
            return PACKET_IS_VALID;
        }
    }
#endif

    printf("framing_error3!\r\n");
    return FRAMING_ERROR;
}

/*! *********************************************************************************
* \brief  This function performs a XOR over the message to compute the CRC
*
* \param[in]  pBuffer - pointer to the messae
* \param[in]  size - the length of the message
*
* \return  the CRC of the message
*
********************************************************************************** */
uint8_t FSCI_computeChecksum( void *pBuffer, uint16_t size )
{
    uint16_t index;
    uint8_t  checksum = 0;

    for ( index = 0; index < size; index++ )
    {
        checksum ^= ((uint8_t*)pBuffer)[index];
    }

    return checksum;
}

/*! *********************************************************************************
* \brief  This function performs the encoding of a message, using the Escape Sequence
*
* \param[in]  pDataIn, pointer to the messae to be encoded
* \param[in]  len, the length of the message
* \param[out]  pDataOut, pointer to the encoded message
*
* \return  The number of bytes added in the new buffer
*
********************************************************************************** */
#if gFsciUseEscapeSeq_c
uint32_t FSCI_encodeEscapeSeq( uint8_t* pDataIn, uint32_t len, uint8_t* pDataOut )
{
    uint32_t index, new_index = 0;

    if( NULL != pDataOut )
    {
        for ( index = 0; index < len; index++ )
        {
            if( (pDataIn[index] == gFSCI_StartMarker_c) ||
               (pDataIn[index] == gFSCI_EndMarker_c)    ||
                   (pDataIn[index] == gFSCI_EscapeChar_c) )
            {
                pDataOut[new_index++] = gFSCI_EscapeChar_c;
                pDataOut[new_index++] = pDataIn[index] ^ gFSCI_EscapeChar_c;
            }
            else
            {
                pDataOut[new_index++] = pDataIn[index];
            }
        }
    }

    return new_index;
}
#endif

/*! *********************************************************************************
* \brief  This function performs the decoding of a message, using the Escape Sequence
*
* \param[in]  pData pointer to the messae to be encoded
* \param[in]  len the length of the message
*
*
********************************************************************************** */
#if gFsciUseEscapeSeq_c
void FSCI_decodeEscapeSeq( uint8_t* pData, uint32_t len )
{
    uint32_t index, new_index;

    /* Find the first gFSCI_EscapeChar_c */
    for ( index = 0; index < len; index++ )
        if ( pData[index] == gFSCI_EscapeChar_c )
            break;

    new_index = index;

    /* If a gFSCI_EscapeChar_c was found, decode the packet in place */
    while ( index < len )
    {
        if ( pData[index] == gFSCI_EscapeChar_c )
        {
            index++; /* skip over the gFSCI_EscapeChar_c */

            if ( index < len )
                pData[new_index++] = pData[index++] ^ gFSCI_EscapeChar_c;
        }
        else if ( new_index != index )
        {
            pData[new_index++] = pData[index++];
        }
    }
}
#endif

#if gFsciRxAck_c && gFsciRxAckTimeoutUseTmr_c
/*! *********************************************************************************
* \brief  This function is the callback of an Ack wait expire for a fsci interface
*
* \param[in]  param fsci interface on which the Ack was expected
*
********************************************************************************** */
static void FSCI_RxAckExpireCb(void *param)
{
    if( mFsciCommData[(uint32_t)param].txRetryCnt )
    {
        mFsciCommData[(uint32_t)param].txRetryCnt--;
    }

    /* Allow retransmission */
    mFsciCommData[(uint32_t)param].ackWaitOngoing = FALSE;
}
#endif

#if gFsciRxTimeout_c && !mFsciRxTimeoutUsePolling_c
/*! *********************************************************************************
* \brief  This function is the callback of an Rx timeout expired for a fsci interface
*
* \param[in]  param fsci interface on which no bytes were received
*
********************************************************************************** */
static void FSCI_RxRxTimeoutCb(void *param)
{
    mFsciCommData[(uint32_t)param].rxTmrExpired = TRUE;
}
#endif

/*! *********************************************************************************
* \brief  This function is used to send a FSCI packet to the serial manager
*
* \param[in]  fsciInterface fsci interface on which the packet is to be sent
* \param[in]  pPacket serial packet to be sent
* \param[in]  packetLen lenght of the serial packet in bytes
*
********************************************************************************** */
static void FSCI_SendPacketToSerialManager(uint32_t fsciInterface, uint8_t *pPacket, uint16_t packetLen)
{
#if gFsciRxAck_c
    fsciComm_t     *pCommData = &mFsciCommData[fsciInterface];
#if !gFsciRxAckTimeoutUseTmr_c
    uint64_t        ackWaitStartTs;
    uint64_t        currentTs;
#endif
#endif

#if gFsciRxAck_c

    OSA_MutexLock(pCommData->syncTxRxAckMutexId, osaWaitForever_c);

    pCommData->ackReceived = FALSE;
    pCommData->txRetryCnt = mFsciTxRetryCnt_c;

    while( pCommData->txRetryCnt )
    {
        Serial_SyncWrite(gFsciSerialInterfaces[fsciInterface], pPacket, packetLen);
        pCommData->ackWaitOngoing = TRUE;

        /* Allow the FSCI interface to receive ACK packet,
           if last packet was received on the same interface */
        if( mFsciSrcInterface == fsciInterface )
        {
            pCommData->pPacketFromClient = NULL;
        }

#if gFsciRxAckTimeoutUseTmr_c
        /* Start timer for ACK wait */
        TMR_StartSingleShotTimer(pCommData->ackWaitTmr, mFsciRxAckTimeoutMs_c, FSCI_RxAckExpireCb, (void*)fsciInterface);

        /* Wait for timer to expire or Ack to be received */
        while( pCommData->ackWaitOngoing && !pCommData->ackReceived )
        {
            FSCI_receivePacket((void*)fsciInterface);
        }

        if( pCommData->ackReceived )
        {
            TMR_StopTimer(pCommData->ackWaitTmr);
            pCommData->ackWaitOngoing = FALSE;
            break;
        }
#else
        /* Get initial timestamp and poll until mFsciRxAckTimeoutMs_c has passed */
        ackWaitStartTs = TMR_GetTimestamp();

        while( !pCommData->ackReceived )
        {
            FSCI_receivePacket((void*)fsciInterface);
            currentTs = TMR_GetTimestamp();
            if( ((currentTs - ackWaitStartTs) / 1000)  > mFsciRxAckTimeoutMs_c )
            {
                pCommData->ackWaitOngoing = FALSE;
                if( pCommData->txRetryCnt )
                {
                    pCommData->txRetryCnt--;
                }
                break; /* Timeout expired */
            }
        }

        if( pCommData->ackReceived )
        {
            pCommData->ackWaitOngoing = FALSE;
            break; /* Success */
        }
#endif
    }

    MEM_BufferFree(pPacket);

    OSA_MutexUnlock(pCommData->syncTxRxAckMutexId);
#else /* gFsciRxAck_c */
#if gFsciUseBlockingTx_c
    if( gFsciTxBlocking )
    {
        Serial_SyncWrite(gFsciSerialInterfaces[fsciInterface], pPacket, packetLen);
        MEM_BufferFree(pPacket);
    }
    else
#endif /* gFsciUseBlockingTx_c */
    {
        if(gSerial_Success_c != Serial_AsyncWrite( gFsciSerialInterfaces[fsciInterface], pPacket, packetLen, (pSerialCallBack_t)FSCI_txCallback, pPacket))
        {
            MEM_BufferFree(pPacket);
        }
    }
#endif /* gFsciRxAck_c */
}

#endif /* gFsciIncluded_c */

Credits

Ashok R

Ashok R

37 projects • 102 followers
Hobbyist/Engineer/Director/Animatior

Comments