This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] EVM430-FR6047:USS

Guru**** 1626620 points
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1337832/evm430-fr6047-uss

器件型号:EVM430-FR6047

你(们)好。

在项目 MSP430FR6043EVM_USS_ADC 中、向我指出读取哪个变量/变量以获取 Water_Demo 捕获。 以及连续捕获。

谢谢。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    我不明白你的观点。 您是指通过将 ADC 捕获数据保存到.csv 文件来获取 ADC 捕获吗? 然后、您可以点击"Save capture"按钮。  

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

     现金、您好!

    我需要帮助来在工程软件中找到将 ADC 捕获和连续捕获发送到 USS 的 GUI 的位置。

    谢谢。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    您应该会在函数 hmi Measurement_Update ()中找到。 有一个名为 CommandHandler_Transmities()的函数可以将 ADC 捕捉数据发送到 GUI。

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    大家好、现金、感谢您提供相关信息。

    请向我指出可读取/更新的 C 代码/函数、GUI 中的以下 USS 参数

    谢谢。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    它位于 HMI_updateUSSParameters()函数中。

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    为了使用上面给出的信息根据我的喜好校准 ADC 信号、我在调用以下函数后是否需要执行校准:

    代码= USS_startLowPowerUltrasonicCapture (&gUssSWConfig);
    代码= USS_runAlgorithms (&gUssSWConfig、&algResults);

    或者我能否在调用这些函数之前进行校准。

    谢谢。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    据我所知、您要做的就是开始低功耗超声波捕获->校准 ADC 信号->运行算法以获取基于校准后的 ADC 信号的结果。

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    以下是我希望完成的代码。

    int main(void)
    {
    #if (APPLICATION_ENABLE_CHANNEL_SWAP == true)
        uint16_t appSwapInterval = APPLICATION_CHANNEL_SWAP_INTERVAL;
    #endif
    
    #if (APPLICATION_ENABLE_ABSTOF_DTOF_OFFSET_CALIBRATION == true)
        USS_dTof_absTof_offset_results abstoFDtofTestResults;
        USS_dTof_absTof_offset_test_config abstoFDtofTestConfig =
        {
             .numOfTestIterations        = APPLICATION_ABSTOF_DTOF_OFFSET_UPDATE_INTERVAL,
             .isUseLPMCapture            = APPLICATION_ABSTOF_DTOF_OFFSET_LPM_CAPTURE,
             .isCalculateUpsAbsTofOffset = APPLICATION_ABSTOF_DTOF_OFFSET_CALC_UPS_ABSTOF,
             .isCalculateDnsAbsTofOffset = APPLICATION_ABSTOF_DTOF_OFFSET_CALC_DNS_ABSTOF,
             .isCalculateDToFOffset      = APPLICATION_ABSTOF_DTOF_OFFSET_CALC_DTOF,
        };
    #endif
    
        volatile USS_message_code code;
        USS_Algorithms_Results algResults;
        USS_calibration_hspll_results testResults;
        int i;
    
    
    
        // Register PLL unlock event
        USS_registerHSPLLInterruptCallback(USS_HSPLL_Interrupt_PLLUNLOCK,
                                           &handlePllUnlockEvent);
    
        code = USS_configureUltrasonicMeasurement(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
    #if((USS_ALG_ABS_TOF_COMPUTATION_MODE == USS_ALG_ABS_TOF_COMPUTATION_MODE_LOBE_WIDE) || \
        (USS_ALG_ABS_TOF_COMPUTATION_MODE == USS_ALG_ABS_TOF_COMPUTATION_MODE_HILBERT_WIDE))
        // Reference binary pattern are only needed by
        // USS_Alg_AbsToF_Calculation_Option_lobeWide and
        // USS_Alg_AbsToF_Calculation_Option_hilbertWide AbsToF computation options
        if((USS_Alg_AbsToF_Calculation_Option_lobeWide ==
                gUssSWConfig.algorithmsConfig->absToFOption)
           || (USS_Alg_AbsToF_Calculation_Option_hilbertWide ==
                   gUssSWConfig.algorithmsConfig->absToFOption))
        {
    #if defined(__MSP430_HAS_SAPH_A__)
            if(USS_measurement_pulse_generation_mode_multi_tone ==
                    gUssSWConfig.measurementConfig->pulseConfig->pulseGenMode)
            {
                code = USS_generateMultiToneBinaryPattern(&gUssSWConfig);
                checkCode(code, USS_message_code_no_error);
            }
    #endif
            if(USS_measurement_pulse_generation_mode_multi_tone !=
                    gUssSWConfig.measurementConfig->pulseConfig->pulseGenMode)
            {
                code = USS_generateMonoDualToneBinaryPattern(&gUssSWConfig);
                checkCode(code, USS_message_code_no_error);
            }
        }
    
    #if (APPLICATION_ENABLE_BINARY_PATTERN_SIZE_SCALING == true)
        gUssSWConfig.algorithmsConfig->binaryPatternLength =
                (gUssSWConfig.captureConfig->sampleSize / APPLICATION_BINARY_PATTERN_SCALE_FACTOR);
    #endif
    
    #endif
    
    
        // Application must ensure no application level interrupts occur while
        // verifying HSPLL Frequency
        disableApplicationInterrupts();
    
        code = USS_verifyHSPLLFrequency(&gUssSWConfig, &testResults);
        checkCode(code, USS_message_code_no_error);
    
        // Application can re-enable interrupts after HSPLL verification
        enableApplicationInterrupts();
    
        gUssSWConfig.algorithmsConfig->clockRelativeError = _IQ27div((int32_t)(testResults.actualTestCount -
                testResults.expectedResult),testResults.expectedResult);
    
        code = USS_initAlgorithms(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
    #if (APPLICATION_ENABLE_SIGNAL_GAIN_CALIBRATION == true)
        code = USS_calibrateSignalGain(&gUssSWConfig);
        checkCode(code, USS_message_code_Signal_Gain_Calibration_successful);
    #endif
    
    #if (APPLICATION_ENABLE_ABSTOF_DTOF_OFFSET_CALIBRATION == true)
        code = USS_calculateOffsets(&gUssSWConfig, &abstoFDtofTestResults,
                                    &abstoFDtofTestConfig);
        checkCode(code, USS_message_code_no_error);
    
        code = USS_updateAdditionalCaptureDelay(&gUssSWConfig,
              ((abstoFDtofTestResults.upsAbsToFOffset + abstoFDtofTestResults.dnsAbsToFOffset) /2.0f) -
              APPLICATION_ABSTOF_REFERENCE);
        checkCode(code, USS_message_code_no_error);
    
        code = USS_updateDtoFOffset(&gUssSWConfig, (-1.0f *abstoFDtofTestResults.dToFOffset));
        checkCode(code, USS_message_code_no_error);
    
    #endif
    
        while(!calibrateUSSParameters()) ; //do until system is calibrated
    
        // Set the background timer period to 1 second
        USS_configAppTimerPeriod(&gUssSWConfig, gUssSWConfig.systemConfig->measurementPeriod);
    
    
        while(1)
        {
            code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
            checkCode(code, USS_message_code_no_error);
    
            code = USS_runAlgorithms(&gUssSWConfig,&algResults);
            checkCode(code, USS_message_code_valid_results);
    
    #if (APPLICATION_ENABLE_CHANNEL_SWAP == true)
            if(appSwapInterval == 0)
            {
                code = USS_swapCaptureChannels(&gUssSWConfig);
                code = USS_swapAlgorithmsCaptureBuffers(&gUssSWConfig);
    
                appSwapInterval = APPLICATION_CHANNEL_SWAP_INTERVAL;
            }else{
                appSwapInterval--;
            }
    #endif
    
            prepareTransmission(algResults.volumeFlowRate);
    
            #if APPLICATION_ENABLE_UART_DEBUG
                for(i=0;i<messageLength;i++){
                    uartTxHexByte(messageBuffer[i]);
                    uartTxByte(' ');
                }
                uartTxByte('\n');
                uartTxByte('\r');
                uartTxUSSResult(APPLICATION_UART_ABSTOF_UPS_DELIM,&algResults.totalTOF_UPS);
                uartTxUSSResult(APPLICATION_UART_ABSTOF_DNS_DELIM,&algResults.totalTOF_DNS);
                uartTxUSSResult(APPLICATION_UART_DTOF_DELIM,&algResults.deltaTOF);
                uartTxUSSResult(APPLICATION_UART_VFR_DELIM,&algResults.volumeFlowRate);
                uartTxByte('\n');
                uartTxByte('\r');
            #endif
            // Wait for timer to elapse
            USS_waitForAppTimerElapse(&gUssSWConfig,USS_low_power_mode_option_low_power_mode_3);
        }
    }
    

    注意第103行、这里是我要设置 USS 参数的位置。  

    请注意、此处是校准例程

    static uint8_t calibrateUSSParameters(void){ //USS_SW_Library_configuration *config
        uint16_t updnsCaptureSize = gUssSWConfig.captureConfig->sampleSize;
        uint8_t* pUPSCap = (uint8_t*)(USS_getUPSPtr(&gUssSWConfig));
        uint8_t* pDNSCap = (uint8_t*)(USS_getDNSPtr(&gUssSWConfig));
        volatile USS_message_code code;
    
        // Perform a low power measurement, going to LPM3 in between UPS and DNS
        code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
        
      //  read pUPSCap and pDNSCap to determin best USS parameters  
        
      // set USS parameters based on Algorithm 
        
      //  update the USS
    
        return 0;
    
    }
    
    

    非常感谢您的评论。  

    非常感谢您的帮助

    以法莲

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!  

    我只是通过系统收到了您的一条消息、但是这与您的最后一条消息完全相同、我感到困惑。

    在最后一封邮件中、我想确认插入代码的位置(第103行、请注意原始 TI 代码)。 是正确的。

    下面是捕获上行和下行数据的代码、目前仅打印这些数据。 我是否正确地进行了采集?

    static uint8_t calibrateUSSParameters(void){ //USS_SW_Library_configuration *config
        uint16_t updnsCaptureSize = gUssSWConfig.captureConfig->sampleSize;
        uint8_t* pUPSCap = (uint8_t*)(USS_getUPSPtr(&gUssSWConfig));
        uint8_t* pDNSCap = (uint8_t*)(USS_getDNSPtr(&gUssSWConfig));
        volatile USS_message_code code;
    
    
        // Perform a low power measurement, going to LPM3 in between UPS and DNS
        code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
      //  determine the best USS parameters based on the up/down stream for best USS parameters
        printBytes(pUPSCap, updnsCaptureSize);
        printBytes(pDNSCap, updnsCaptureSize);
    
      // set USS parameters based on Aquasmart Algorithm
        gUssSWConfig.captureConfig->gainRange = (USS_Capture_Gain_Range )50; //USS_GAIN_RANGE;
        gUssSWConfig.measurementConfig->startADCsamplingCount = 350; //(uint16_t)USS_ADC_SAMP_COUNT;
        gUssSWConfig.measurementConfig->startPPGCount = 125;  //USS_START_CAPTURE_SEC;
        gUssSWConfig.measurementConfig->turnOnADCCount = 250; //(uint_least16_t) USS_TURN_ON_ADC_COUNT;
        gUssSWConfig.algorithmsConfig->negSearchRange = 100; //USS_ALG_ABS_TOF_NEG_SEARCH_RANGE;
        gUssSWConfig.algorithmsConfig->ratioOfTrackLobeToPeak = 10.72; //USS_ALG_RATIO_OF_TRACK_LOBE
    
      //  update the USS
    
        return 0;
    
    }
    

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    没关系、我认为您在上一篇文章中已经解决了您的问题。  

    我看到大家在捕获信号后、会尝试更新增益范围 startADCamplingCount、startPPGCount 和 trunOnADCCount。 "你干嘛啊? 这些参数不会对算法的运行产生影响。  

    对于参数 negSearchRange 和 ratioOfTrackBeToPeak,这两个参数确实会影响算法的运行。 但您为什么需要在这里更改它?

    您是否正在进行多渠道水表项目?  

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    我分配的重点是、在运行时使用 algResults.volumeFlowRate 值之前、允许使用 USS GUI 中的参数/advanced 参数调整校准并更新 USSLib。

    关于实际参数和值、我留待稍后处理、请注意、我不了解这些参数的含义和用途、这些参数稍后将进行 Δ-Σ 转换。

     使用给定的模板项目 FR6043_USSSWLib_TEMPLATE 总结我的即时请求

    1.在 main 中最好调用 calibrateUSSParameters()函数。 现在、我在 while (1)循环之前从 main 调用它。 这种行为合适吗?

    2.在 calibrateUSSParameters()中我按如下方式捕获 ADC 信号:  
    代码= USS_startLowPowerUltrasonicCapture (&gUssSWConfig);
    CheckCode (code、USS_MESSAGE_CODE_NO_ERROR);

    这将捕获信号并将向上/向下信号和大小存储在这些变量中吗?  

    uint16_t updnsCaptureSize = gUssSWConfig.captureConfig->sampleSize;
    uint8_t* pUPSCap =(uint8_t*)(USS_getUPSPtr (&gUssSWConfig));
    uint8_t* pDNSCap =(uint8_t*)(USS_getDNSPtr (&gUssSWConfig));

    现在、我将调用一系列函数、每个函数用于校准参数。 我假设  可以在这些函数中使用全局结构 gUssSWConfig、以更改默认值。 因此、我不需要将结构体传入函数中。

    最后,我将使用与 HMI_updateUSSParameters()中相同的函数来更新 USSLib  

    下面是函数的代码:

    static uint8_t calibrateUSSParameters(void){ //USS_SW_Library_configuration *config
        uint16_t updnsCaptureSize = gUssSWConfig.captureConfig->sampleSize;
        uint8_t* pUPSCap = (uint8_t*)(USS_getUPSPtr(&gUssSWConfig));
        uint8_t* pDNSCap = (uint8_t*)(USS_getDNSPtr(&gUssSWConfig));
        volatile USS_message_code code;
    
    
        // Perform a low power measurement, going to LPM3 in between UPS and DNS
        code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
        calibrateGainRang(pUPSCap,pDNSCap,updnsCaptureSize);                   //(USS_Capture_Gain_Range )50; //USS_GAIN_RANGE;
        calibrateStartADCsamplingCount(pUPSCap,pDNSCap,updnsCaptureSize);      //(uint16_t)USS_ADC_SAMP_COUNT;
        calibrateStartPPGCount(pUPSCap,pDNSCap,updnsCaptureSize);              //USS_START_CAPTURE_SEC;
        calibrateTurnOnADCCount(pUPSCap,pDNSCap,updnsCaptureSize);             //(uint_least16_t) USS_TURN_ON_ADC_COUNT;
        calibrateNegSearchRang(pUPSCap,pDNSCap,updnsCaptureSize);              //USS_ALG_ABS_TOF_NEG_SEARCH_RANGE;
        calibrateRatioOfTrackLobeToPeak(pUPSCap,pDNSCap,updnsCaptureSize);     //USS_ALG_RATIO_OF_TRACK_LOBE
    
      //  update the USSLib
        _update_USSmeterConfig();
        _update_USSpllConfiguration();
        _update_USSmeasurementConfig();
        _update_USScaptureConfig();
        _update_USSalgorithmsConfig();
        _update_USSsleepDuration();
    
        return 0;
    
    }
     

     非常感谢您的支持

    以法莲

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    这是上一封电子邮件的延续。 希望大家不会感到困惑。

    看着你指示我的更新函数的代码。:

    UPDATE_USSmeterConfig ();
    _UPDATE_USSpllConfiguration ();
    _update_USSmeasurementConfig ();
    _update_USScaptureConfig();
    _UPDATE_USSalgorithmsConfig();
    _update_USSsleepDuration();

    它们似乎主要更新 USS GUI。 这不是我想做的事。  

    我想使用新参数更新 USSLib 并重新启动算法。

    我看到全局结构  gUssSWConfig 赋予我访问我要修改的所有参数的权限。

    修改后有例程重新启动算法、或者应该只是等待软件进入无限循环、算法将从新值开始。  

        while(1)
        {
            code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
            checkCode(code, USS_message_code_no_error);
    
            code = USS_runAlgorithms(&gUssSWConfig,&algResults);
            checkCode(code, USS_message_code_valid_results);

    请确认。

    非常感谢您的帮助。

    以法莲

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    我有点理解您要在这里做什么。  

    您需要在软件运行时更改参数/高级参数。 是这样吗?

    为此,您可以 先更改 gUssSWConfig 中的参数,然后调用 USS_initAlgorithms() 函数。 此函数将更新您的参数。 您可以更新参数并在软件完成一次测量后调用该函数、该测量在 runAlgorithms 函数之后和下一次 ADC 捕获之前完成。  

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    是的、这正是我的任务。

    请确认我的逻辑、

    我在无限循环之前调用我的例程 calibrateUSSLibParameters ()

        // Set the background timer period to 1 second
        USS_configAppTimerPeriod(&gUssSWConfig, gUssSWConfig.systemConfig->measurementPeriod);
    
        calibrateUSSLibParameters();
        
        while(1)
        {
            code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
            checkCode(code, USS_message_code_no_error);
    
            code = USS_runAlgorithms(&gUssSWConfig,&algResults);
            checkCode(code, USS_message_code_valid_results);
    

    在我的函数  calibrateUSSLibParameters ()中  

    static void calibrateUSSLibParameters(void){ //USS_SW_Library_configuration *config
        uint16_t updnsCaptureSize = gUssSWConfig.captureConfig->sampleSize;
        uint8_t* pUPSCap = (uint8_t*)(USS_getUPSPtr(&gUssSWConfig));
        uint8_t* pDNSCap = (uint8_t*)(USS_getDNSPtr(&gUssSWConfig));
        volatile USS_message_code code;
    
        
        // Perform a low power measurement, going to LPM3 in between UPS and DNS
        code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
        code = USS_runAlgorithms(&gUssSWConfig,&algResults);
        checkCode(code, USS_message_code_valid_results);
    
        
        //show data before update
        printBytes(pUPSCap, updnsCaptureSize);
        printBytes(pDNSCap, updnsCaptureSize);
    
        calibrateGainRang(pUPSCap,pDNSCap,updnsCaptureSize);                   //(USS_Capture_Gain_Range )50; //USS_GAIN_RANGE;
        calibrateStartADCsamplingCount(pUPSCap,pDNSCap,updnsCaptureSize);      //(uint16_t)USS_ADC_SAMP_COUNT;
        calibrateStartPPGCount(pUPSCap,pDNSCap,updnsCaptureSize);              //USS_START_CAPTURE_SEC;
        calibrateTurnOnADCCount(pUPSCap,pDNSCap,updnsCaptureSize);             //(uint_least16_t) USS_TURN_ON_ADC_COUNT;
        calibrateNegSearchRang(pUPSCap,pDNSCap,updnsCaptureSize);              //USS_ALG_ABS_TOF_NEG_SEARCH_RANGE;
        calibrateRatioOfTrackLobeToPeak(pUPSCap,pDNSCap,updnsCaptureSize);     //USS_ALG_RATIO_OF_TRACK_LOBE
    
        //show data after update
        printBytes(pUPSCap, updnsCaptureSize);
        printBytes(pDNSCap, updnsCaptureSize);
        
        //update the USS Lib with new parameters
        code = USS_initAlgorithms(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    }
    

    在这里、我调用两个例程来获得一次测量。

      // Perform a low power measurement, going to LPM3 in between UPS and DNS
        code = USS_startLowPowerUltrasonicCapture(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);
    
        code = USS_runAlgorithms(&gUssSWConfig,&algResults);
        checkCode(code, USS_message_code_valid_results);

    然后在 gUssSWConfig 中设置我的值。 我调用 init 函数。

       //update the USS Lib with new parameters
        code = USS_initAlgorithms(&gUssSWConfig);
        checkCode(code, USS_message_code_no_error);

    非常感谢您的指导。

    以法莲

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    我觉得很好。  

    如果您要在运行时更改中的参数、请执行以下操作。 您可以将  calibrateUSSLibParameters ()放入 while 循环中。 如下所示。

    while (1)

    {

    calibrateUSSLibParameters () ;

    代码= USS_startLowPowerUltrasonicCapture (&gUssSWConfig);
    CheckCode (code、USS_MESSAGE_CODE_NO_ERROR);

    代码= USS_runAlgorithms (&gUssSWConfig、&algResults);
    CheckCode (code、USS_MESSAGE_CODE_VALID_RESULTS);

    此致、

    现金豪

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现金、您好!

    非常感谢您的帮助。

    您可以关闭此案例。

    以法莲