谷动谷力

 找回密码
 立即注册
查看: 1493|回复: 0
打印 上一主题 下一主题
收起左侧

STM32H5测评 | ADC采集和设置测试、串口通讯测试、TCP/IP UDP通...

[复制链接]
跳转到指定楼层
楼主
发表于 2023-8-2 08:40:39 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 sunsili 于 2024-1-11 16:26 编辑

STM32H5测评 | ADC采集和设置测试、串口通讯测试、TCP/IP UDP通讯测试
来源:EEWORLD论坛网友 bigbat 版权归原作者所有


ADC采集和设置测试、串口通讯测试要测试STM32H5的ADC,首先参考一下例程 ADC_SingleConversion_TriggerSW_IT,这是 cube 自带的例程,打开 cube 先看一下设置,例程使用了引脚 PC0,这个脚和板子的 A1 相连接。STM32H5 有两个 ADC 核心,设置为ADC1和CH10,引脚信号为单端信号 Single-ended。
引脚设置关键是时钟速率 Clock Prescaler 和 Resolution,速率为4分频,分辨率为12,需要中断开启。转换组设置 ADC_Regular_ConversionMode 为软件开启,而不是使用硬件联动触发的方式。
  1. /* Private variables ---------------------------------------------------------*/
  2. ADC_HandleTypeDef hadc1;

  3. /* USER CODE BEGIN PV */

  4. /* Variables for ADC conversion data */
  5. __IO uint16_t uhADCxConvertedData = VAR_CONVERTED_DATA_INIT_VALUE;; /* ADC group regular conversion data */

  6. /* Variables for ADC conversion data computation to physical values */
  7. uint16_t uhADCxConvertedData_Voltage_mVolt = 0;  /* Value of voltage calculated from ADC conversion data (unit: mV) */

  8. /* Variable to report status of ADC group regular unitary conversion          */
  9. /*  0: ADC group regular unitary conversion is not completed                  */
  10. /*  1: ADC group regular unitary conversion is completed                      */
  11. /*  2: ADC group regular unitary conversion has not been started yet          */
  12. /*     (initial state)                                                        */
  13. __IO uint8_t ubAdcGrpRegularUnitaryConvStatus = 2; /* Variable set into ADC interruption callback */

  14. /* USER CODE END PV */
复制代码

程序定义了一个 ADC 的转换变量 uhADCxConvertedData,注意:这里必须声明为 "__IO uint16_t",这个是为了让编译器不去优化该变量,直接去地址寄存器取值,在高速 MCU 中很关键,尤其在开启 ICACHE 的状态下。还有一个状态变量 ubAdcGrpRegularUnitaryConvStatus,用来做转换标准。
转换程序的演示程序
  1. /* Perform ADC calibration *///校准开始
  2. if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK)
  3. {
  4.    /* Calibration Error */
  5.    Error_Handler();
  6. }

  7. /* USER CODE END 2 */

  8. /* Infinite loop */
  9. /* USER CODE BEGIN WHILE */
  10. while (1)
  11. {
  12.    /* Start ADC group regular conversion */
  13.    //转换开始
  14.    if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
  15.    {
  16.      /* Error: ADC conversion start could not be performed */
  17.      Error_Handler();
  18.    }

  19.    /* For this example purpose, wait until conversion is done */
  20.    //等待转换结束,出结果
  21.    while (ubAdcGrpRegularUnitaryConvStatus != 1);

  22.    /* Reset status variable of ADC group regular unitary conversion */
  23.    ubAdcGrpRegularUnitaryConvStatus = 0;

  24.    /* Toggle LED at each ADC conversion */
  25.    BSP_LED_On(LED1);
  26.    HAL_Delay(LED_BLINK_SLOW);
  27.    BSP_LED_Off(LED1);
  28.    HAL_Delay(LED_BLINK_SLOW);

  29.    /* Note: ADC group regular conversions data are stored into array         */
  30.    /*       "uhADCxConvertedData"                                            */
  31.    /*       (for debug: see variable content into watch window).             */

  32.    /* Note: ADC conversion data are computed to physical values              */
  33.    /*       into array "uhADCxConvertedData_Voltage_mVolt" using             */
  34.    /*       ADC LL driver helper macro "__LL_ADC_CALC_DATA_TO_VOLTAGE()"     */
  35.    /*       (for debug: see variable content with debugger)                  */
  36.    /*       in IRQ handler callback function.                                */

  37.    /* USER CODE END WHILE */

  38.    /* USER CODE BEGIN 3 */
  39. }
  40. /* USER CODE END 3 */
  41. }
复制代码

过程很简单,先初始化 ADC,在测试前先校准 ADC,然后开始转换 ADC 测量,等待 ADC 转换结束,这个程序用了中断程序。
  1. /**
  2. * @brief This function handles ADC1 global interrupt.
  3. */
  4. void ADC1_IRQHandler(void)
  5. {
  6. /* USER CODE BEGIN ADC1_IRQn 0 */

  7. /* Customize process using LL interface to improve the performance          */
  8. /* (exhaustive feature management not handled).                             */

  9. /* ########## Starting from this point HAL API must not be used ########### */

  10. /* Check whether ADC group regular end of unitary conversion caused         */
  11. /* the ADC interruption.                                                    */
  12. if(LL_ADC_IsActiveFlag_EOC(ADC1) != 0)
  13. {
  14.    /* Clear flag ADC group regular end of unitary conversion */
  15.    LL_ADC_ClearFlag_EOC(ADC1);

  16.    /* Call interruption treatment function */
  17.    AdcGrpRegularUnitaryConvComplete_Callback();
  18. }

  19. /* Check whether ADC group regular overrun caused the ADC interruption */
  20. if(LL_ADC_IsActiveFlag_OVR(ADC1) != 0)
  21. {
  22.    /* Clear flag ADC group regular overrun */
  23.    LL_ADC_ClearFlag_OVR(ADC1);

  24.    /* Call interruption treatment function */
  25.    AdcGrpRegularOverrunError_Callback();
  26. }

  27. /* USER CODE END ADC1_IRQn 0 */
  28. /* USER CODE BEGIN ADC1_IRQn 1 */

  29. /* USER CODE END ADC1_IRQn 1 */
  30. }
复制代码




中断处理也很简单,首先判断是不是 ADC1 的"测量规则组"转换是否结束是则清除,完成后调用 AdcGrpRegularUnitaryConvComplete_Callback 函数,该函数中对主函数中的标志变量 ubAdcGrpRegularUnitaryConvStatus 进行设置。判断是否是 ADC1 中断转换完成标志,是则清除。
  1. /**
  2. * @brief  ADC group regular end of unitary conversion interruption callback
  3. * @retval None
  4. */void AdcGrpRegularUnitaryConvComplete_Callback()
  5. {
  6. /* Retrieve ADC conversion data */
  7. uhADCxConvertedData = LL_ADC_REG_ReadConversionData32(ADC1);

  8. /* Computation of ADC conversions raw data to physical values           */
  9. /* using helper macro.                                                  */
  10. uhADCxConvertedData_Voltage_mVolt = __LL_ADC_CALC_DATA_TO_VOLTAGE(VDDA_APPLI, uhADCxConvertedData, LL_ADC_RESOLUTION_12B);

  11. /* Update status variable of ADC unitary conversion                     */
  12. ubAdcGrpRegularUnitaryConvStatus = 1;
  13. }

  14. /**
  15. * @brief  ADC group regular overrun interruption callback
  16. * @note   This function is executed when ADC group regular
  17. *         overrun error occurs.
  18. * @retval None
  19. */
  20. void AdcGrpRegularOverrunError_Callback(void)
  21. {
  22. /* Note: Disable ADC interruption that caused this error before entering in
  23.           infinite loop below. */

  24. /* In case of error due to overrun: Disable ADC group regular overrun interruption */
  25. LL_ADC_DisableIT_OVR(ADC1);

  26. /* Error reporting */
  27. Error_Handler();
  28. }
复制代码


例程中没有输出变量,接下来的任务就是通过串口输出 ADC1 的值了。



根据板子的设计,将串口输出到 USART3_PD9,USART3_PD8。通过新建立的项目完成硬件配置,主要是将 ADC 的函数拷贝到项目中。
  1. /**
  2. * @brief USART3 Initialization Function
  3. * @param None
  4. * @retval None
  5. */static void MX_USART3_UART_Init(void)
  6. {

  7. /* USER CODE BEGIN USART3_Init 0 */

  8. /* USER CODE END USART3_Init 0 */

  9. /* USER CODE BEGIN USART3_Init 1 */

  10. /* USER CODE END USART3_Init 1 */
  11. huart3.Instance = USART3;
  12. huart3.Init.BaudRate = 115200;
  13. huart3.Init.WordLength = UART_WORDLENGTH_8B;
  14. huart3.Init.StopBits = UART_STOPBITS_1;
  15. huart3.Init.Parity = UART_PARITY_NONE;
  16. huart3.Init.Mode = UART_MODE_TX_RX;
  17. huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  18. huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  19. huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  20. huart3.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  21. huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  22. if (HAL_UART_Init(&huart3) != HAL_OK)
  23. {
  24.    Error_Handler();
  25. }
  26. if (HAL_UARTEx_SetTxFifoThreshold(&huart3, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  27. {
  28.    Error_Handler();
  29. }
  30. if (HAL_UARTEx_SetRxFifoThreshold(&huart3, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  31. {
  32.    Error_Handler();
  33. }
  34. if (HAL_UARTEx_DisableFifoMode(&huart3) != HAL_OK)
  35. {
  36.    Error_Handler();
  37. }
  38. /* USER CODE BEGIN USART3_Init 2 */

  39. /* USER CODE END USART3_Init 2 */

  40. }
复制代码


将例程拷贝到例程中,然后是 printf 函数的设置,main.c 中包含 #include <stdio.h>
  1. #if defined(__ICCARM__)
  2. /* New definition from EWARM V9, compatible with EWARM8 */
  3. int iar_fputc(int ch);
  4. #define PUTCHAR_PROTOTYPE int putchar(int ch)
  5. #elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
  6. /* ARM Compiler 5/6*/
  7. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
  8. #elif defined(__GNUC__)
  9. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
  10. #endif /* __ICCARM__ */
  11. 还有就是声明 PUTCHAR_PROTOTYPE 宏。
  12. /* USER CODE BEGIN 4 */
  13. /**
  14. * @brief  Retargets the C library printf function to the USART.
  15. * @param  None
  16. * @retval None
  17. */
  18. PUTCHAR_PROTOTYPE
  19. {
  20. /* Place your implementation of fputc here */
  21. /* e.g. write a character to the USART1 and Loop until the end of transmission */
  22. HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);

  23. return ch;
  24. }
  25. /* USER CODE END 4 */
复制代码

还需要一个 keil 的设置 MicroLib。


完成这些设置后就可以使用printf函数了。在主函数中,
  1. printf("mVolt=%d \n",uhADCxConvertedData_Voltage_mVolt);  
  2. while (1)
  3. {
  4.    /* USER CODE END WHILE */
  5. /* Start ADC group regular conversion */
  6.    if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
  7.    {
  8.      /* Error: ADC conversion start could not be performed */
  9.      Error_Handler();
  10.    }

  11.    /* For this example purpose, wait until conversion is done */
  12.    while (ubAdcGrpRegularUnitaryConvStatus != 1);
  13.    printf("mVolt=%d \n",uhADCxConvertedData_Voltage_mVolt);  
  14.    /* Reset status variable of ADC group regular unitary conversion */
  15.    ubAdcGrpRegularUnitaryConvStatus = 0;
  16. HAL_Delay(500);
  17.    /* USER CODE BEGIN 3 */
  18. }
复制代码

下面是输出的结果
测试中我使用了我的一个宝贝,信号发生器。ADC 的性能和以往的ST差不多。

TCP/IP UDP 通讯测试

我的项目在以前的应用主要是 FreeRTOS+lwIP 的组合,这个组合能用但不好用,其中问题也较多。例如,断线重连问题,从检测到重连的时间较长,往往中断50秒或更久,希望找到一个靠谱的 TCP/IP 协议栈。STM32H563ZI 板卡有 ThreadX 和 NETX 的组合支持,我们一起来测试一下。

首先从已有例程开始,选择 Nx_SNTP_Client,该例程是 NTP 服务器对时的应用。这个应用非常实用,在与实时时钟的相关应用中,多数都需要校时任务。首先打开 Nx_SNTP_Client.ioc 文件。

可以看到应用支持,ThreadX 和 NETX 的应用,应用层支持 DNS 和 SNTP 应用,相比以前使用过lwIP的DNS,步骤简洁很多。底层支持 DHCP、ICMP、TCP,比 lwIP 的 DHCP 稳定快捷,lwIP 的 DHCP 应用有时会出现长时间无法获得IP、需不停检测和重试的情况。硬件接口设置就很简单了接口选 RMII,PHY 设置 LAN8742,MAC 地址 00:80:E1:00:00:00 (这是测试地址,应用中最好修改一下)硬件开启全局中断。还有串口设置,还有 printf 设置,注意编译时选择 microLIB 库,要不无法收到串口信息。下面来分析应用,Main.C 基本上不需要动,主要是硬件程序化和 MX_ThreadX_Init() 函数。
  1. /**
  2.   * @brief The application entry point.
  3.   * @retval int
  4.   */
  5. int main(void)
  6. {
  7.   /* USER CODE BEGIN 1 */
  8.   /* USER CODE END 1 */
  9.   /* MCU Configuration--------------------------------------------------------*/
  10.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  11.   HAL_Init();
  12.   /* USER CODE BEGIN Init */
  13.   /* USER CODE END Init */
  14.   /* Configure the system clock */
  15.   SystemClock_Config();
  16.   /* USER CODE BEGIN SysInit */
  17.   /* USER CODE END SysInit */
  18.   /* Initialize all configured peripherals */
  19.   MX_GPIO_Init();
  20.   MX_ICACHE_Init();
  21.   MX_ETH_Init();
  22.   MX_USART3_UART_Init();
  23.   MX_RTC_Init();
  24.   /* USER CODE BEGIN 2 */
  25.   /* USER CODE END 2 */
  26.   MX_ThreadX_Init();
  27.   /* We should never get here as control is now taken by the scheduler */
  28.   /* Infinite loop */
  29.   /* USER CODE BEGIN WHILE */
  30.   while (1)
  31.   {
  32.     /* USER CODE END WHILE */
  33.     /* USER CODE BEGIN 3 */
  34.   }
  35.   /* USER CODE END 3 */
  36. }
复制代码

可以看到程序非常简洁,微软的建议是将硬件的初始化尽可能的放在 main.C 中进行,这里放入 printf 的设置。不要放入太多的逻辑代码,应用的初始化尽可能在 tx_application_define 函数中进行。这里的初始化过程被封装到 status = MX_NetXDuo_Init(memory_ptr) 中了,在这个 MX_NetXDuo_Init 函数中在进行线程的初始化。主要有三个线程和一个回调函数。
  1. /* Private function prototypes -----------------------------------------------*/
  2. static VOID App_Main_Thread_Entry (ULONG thread_input);
  3. static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);
  4. /* USER CODE BEGIN PFP */
  5. static VOID App_SNTP_Thread_Entry(ULONG thread_input);
  6. static VOID App_Link_Thread_Entry(ULONG thread_input);
复制代码


对我启发较大的是 App_Link_Thread_Entry 线程,这个线程会定期检测网络物理连接。
接下来是 App_Main_Thread_Entry 线程,这个线程主要是完成联网的准备工作。还有就是对 App_Link_Thread_Entry 线程数据的初始化。
  1. /* Exported macro ------------------------------------------------------------*/
  2. /* USER CODE BEGIN EM */
  3. #define PRINT_IP_ADDRESS(addr)    do { \
  4.              printf("STM32 %s: %lu.%lu.%lu.%lu \n", #addr, \
  5.              (addr >> 24) & 0xff,                          \
  6.              (addr >> 16) & 0xff,                          \
  7.              (addr >> 8) & 0xff,                           \
  8.              (addr & 0xff));                               \
  9.              } while(0)
复制代码


主要的工作线程是 App_SNTP_Thread_Entry,这个线程完成整个过程。
  1. /**
  2. * @brief  SNTP thread entry.
  3. * @param thread_input: ULONG user argument used by the thread entry
  4. * @retval none
  5. */
  6. /* Define the client thread.  */
  7. static void App_SNTP_Thread_Entry(ULONG info)
  8. {
  9.   UINT ret;
  10.   RtcHandle.Instance = RTC;
  11.   ULONG  seconds, fraction;
  12.   ULONG  events = 0;
  13.   UINT   server_status;
  14.   NXD_ADDRESS sntp_server_ip;
  15.   NX_PARAMETER_NOT_USED(info);
  16.   sntp_server_ip.nxd_ip_version = 4;
  17.   /* Create a DNS client */
  18.   ret = dns_create(&DnsClient);
  19.   if (ret != NX_SUCCESS)
  20.   {
  21.     Error_Handler();
  22.   }
  23.   /* Look up SNTP Server address. */
  24.   ret = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)SNTP_SERVER_NAME,
  25.                                 &sntp_server_ip.nxd_ip_address.v4, NX_APP_DEFAULT_TIMEOUT);
  26.     /* Check for error. */
  27.   if (ret != NX_SUCCESS)
  28.   {
  29.     Error_Handler();
  30.   }
  31.    /* Create the SNTP Client */
  32.   ret =  nx_sntp_client_create(&SntpClient, &NetXDuoEthIpInstance, iface_index, &NxAppPool, NULL, kiss_of_death_handler, NULL);
  33.   /* Check for error. */
  34.   if (ret != NX_SUCCESS)
  35.   {
  36.     Error_Handler();
  37.   }
  38.   /* Setup time update callback function. */
  39.    nx_sntp_client_set_time_update_notify(&SntpClient, time_update_callback);
  40.   /* Use the IPv4 service to set up the Client and set the IPv4 SNTP server. */
  41.   ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_ip.nxd_ip_address.v4);
  42.   if (ret != NX_SUCCESS)
  43.   {
  44.     Error_Handler();
  45.   }
  46.   /* Run whichever service the client is configured for. */
  47.   ret = nx_sntp_client_run_unicast(&SntpClient);
  48.   if (ret != NX_SUCCESS)
  49.   {
  50.     Error_Handler();
  51.   }
  52.   else
  53.   {
  54.     PRINT_CNX_SUCC();
  55.   }
  56.   /* Wait for a server update event. */
  57.   tx_event_flags_get(&SntpFlags, SNTP_UPDATE_EVENT, TX_OR_CLEAR, &events, PERIODIC_CHECK_INTERVAL);
  58.   if (events == SNTP_UPDATE_EVENT)
  59.   {
  60.     /* Check for valid SNTP server status. */
  61.     ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);
  62.     if ((ret != NX_SUCCESS) || (server_status == NX_FALSE))
  63.     {
  64.       /* We do not have a valid update. */
  65.       Error_Handler();
  66.     }
  67.     /* We have a valid update.  Get the SNTP Client time.  */
  68.     ret = nx_sntp_client_get_local_time_extended(&SntpClient, &seconds, &fraction, NX_NULL, 0);
  69.     ret = nx_sntp_client_utility_display_date_time(&SntpClient,buffer,64);
  70.     if (ret != NX_SUCCESS)
  71.     {
  72.       printf("Internal error with getting local time 0x%x\n", ret);
  73.       Error_Handler();
  74.     }
  75.     else
  76.     {
  77.       printf("\nSNTP update :\n");
  78.       printf("%s\n\n",buffer);
  79.     }
  80.   }
  81.   else
  82.   {
  83.     Error_Handler();
  84.   }
  85.   /* Set Current time from SNTP TO RTC */
  86.   rtc_time_update(&SntpClient);
  87.   /* We can stop the SNTP service if for example we think the SNTP server has stopped sending updates */
  88.   ret = nx_sntp_client_stop(&SntpClient);
  89.   if (ret != NX_SUCCESS)
  90.   {
  91.     Error_Handler();
  92.   }
  93.   /* When done with the SNTP Client, we delete it */
  94.   ret = nx_sntp_client_delete(&SntpClient);
  95.   if (ret != NX_SUCCESS)
  96.   {
  97.     Error_Handler();
  98.   }
  99.   /* Toggling LED after a success Time update */
  100.   while(1)
  101.   {
  102.     /* Display RTC time each second  */
  103.     display_rtc_time(&RtcHandle);
  104.     HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
  105.     /* Delay for 1s */
  106.     tx_thread_sleep(100);
  107.   }
  108. }
复制代码


这个程序目的是完成 SNTP 获取,设置到操作系统参数和 RTC 设置中,就可以不断地获取时间了。
可以看到时间显示出来了。但这里需要注意一个“坑”,就是发现时间好像和计算机显示的时间对不上,因为这个时间与UTC时间在不同时区,随即找到以下程序,
  1. /* EPOCH_TIME_DIFF is equivalent to 70 years in sec
  2. calculated with www.epochconverter.com/date-difference
  3. This constant is used to delete difference between :
  4. Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
  5. time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF;
复制代码


将程序修改成:
  1. /* EPOCH_TIME_DIFF is equivalent to 70 years in sec
  2. calculated with www.epochconverter.com/date-difference
  3. This constant is used to delete difference between :
  4. Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
  5. time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF + 28800U;
复制代码

这样转换成 UTC 0800 时区时间。

这次时间匹配成功。


后记


这个程序经长达3小时测试,未发现任何中断现象。反复的ping单片机地址,TTL 值一直很稳定,相比 lwIP 程序改善很多。
+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-12-28 12:57 , Processed in 0.094663 second(s), 44 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表