本帖最后由 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 为软件开启,而不是使用硬件联动触发的方式。- /* Private variables ---------------------------------------------------------*/
- ADC_HandleTypeDef hadc1;
- /* USER CODE BEGIN PV */
- /* Variables for ADC conversion data */
- __IO uint16_t uhADCxConvertedData = VAR_CONVERTED_DATA_INIT_VALUE;; /* ADC group regular conversion data */
- /* Variables for ADC conversion data computation to physical values */
- uint16_t uhADCxConvertedData_Voltage_mVolt = 0; /* Value of voltage calculated from ADC conversion data (unit: mV) */
- /* Variable to report status of ADC group regular unitary conversion */
- /* 0: ADC group regular unitary conversion is not completed */
- /* 1: ADC group regular unitary conversion is completed */
- /* 2: ADC group regular unitary conversion has not been started yet */
- /* (initial state) */
- __IO uint8_t ubAdcGrpRegularUnitaryConvStatus = 2; /* Variable set into ADC interruption callback */
- /* USER CODE END PV */
复制代码
程序定义了一个 ADC 的转换变量 uhADCxConvertedData,注意:这里必须声明为 "__IO uint16_t",这个是为了让编译器不去优化该变量,直接去地址寄存器取值,在高速 MCU 中很关键,尤其在开启 ICACHE 的状态下。还有一个状态变量 ubAdcGrpRegularUnitaryConvStatus,用来做转换标准。 转换程序的演示程序- /* Perform ADC calibration *///校准开始
- if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK)
- {
- /* Calibration Error */
- Error_Handler();
- }
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* Start ADC group regular conversion */
- //转换开始
- if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
- {
- /* Error: ADC conversion start could not be performed */
- Error_Handler();
- }
- /* For this example purpose, wait until conversion is done */
- //等待转换结束,出结果
- while (ubAdcGrpRegularUnitaryConvStatus != 1);
- /* Reset status variable of ADC group regular unitary conversion */
- ubAdcGrpRegularUnitaryConvStatus = 0;
- /* Toggle LED at each ADC conversion */
- BSP_LED_On(LED1);
- HAL_Delay(LED_BLINK_SLOW);
- BSP_LED_Off(LED1);
- HAL_Delay(LED_BLINK_SLOW);
- /* Note: ADC group regular conversions data are stored into array */
- /* "uhADCxConvertedData" */
- /* (for debug: see variable content into watch window). */
- /* Note: ADC conversion data are computed to physical values */
- /* into array "uhADCxConvertedData_Voltage_mVolt" using */
- /* ADC LL driver helper macro "__LL_ADC_CALC_DATA_TO_VOLTAGE()" */
- /* (for debug: see variable content with debugger) */
- /* in IRQ handler callback function. */
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
复制代码
过程很简单,先初始化 ADC,在测试前先校准 ADC,然后开始转换 ADC 测量,等待 ADC 转换结束,这个程序用了中断程序。 - /**
- * @brief This function handles ADC1 global interrupt.
- */
- void ADC1_IRQHandler(void)
- {
- /* USER CODE BEGIN ADC1_IRQn 0 */
- /* Customize process using LL interface to improve the performance */
- /* (exhaustive feature management not handled). */
- /* ########## Starting from this point HAL API must not be used ########### */
- /* Check whether ADC group regular end of unitary conversion caused */
- /* the ADC interruption. */
- if(LL_ADC_IsActiveFlag_EOC(ADC1) != 0)
- {
- /* Clear flag ADC group regular end of unitary conversion */
- LL_ADC_ClearFlag_EOC(ADC1);
- /* Call interruption treatment function */
- AdcGrpRegularUnitaryConvComplete_Callback();
- }
- /* Check whether ADC group regular overrun caused the ADC interruption */
- if(LL_ADC_IsActiveFlag_OVR(ADC1) != 0)
- {
- /* Clear flag ADC group regular overrun */
- LL_ADC_ClearFlag_OVR(ADC1);
- /* Call interruption treatment function */
- AdcGrpRegularOverrunError_Callback();
- }
- /* USER CODE END ADC1_IRQn 0 */
- /* USER CODE BEGIN ADC1_IRQn 1 */
- /* USER CODE END ADC1_IRQn 1 */
- }
复制代码
中断处理也很简单,首先判断是不是 ADC1 的"测量规则组"转换是否结束是则清除,完成后调用 AdcGrpRegularUnitaryConvComplete_Callback 函数,该函数中对主函数中的标志变量 ubAdcGrpRegularUnitaryConvStatus 进行设置。判断是否是 ADC1 中断转换完成标志,是则清除。- /**
- * @brief ADC group regular end of unitary conversion interruption callback
- * @retval None
- */void AdcGrpRegularUnitaryConvComplete_Callback()
- {
- /* Retrieve ADC conversion data */
- uhADCxConvertedData = LL_ADC_REG_ReadConversionData32(ADC1);
- /* Computation of ADC conversions raw data to physical values */
- /* using helper macro. */
- uhADCxConvertedData_Voltage_mVolt = __LL_ADC_CALC_DATA_TO_VOLTAGE(VDDA_APPLI, uhADCxConvertedData, LL_ADC_RESOLUTION_12B);
- /* Update status variable of ADC unitary conversion */
- ubAdcGrpRegularUnitaryConvStatus = 1;
- }
- /**
- * @brief ADC group regular overrun interruption callback
- * @note This function is executed when ADC group regular
- * overrun error occurs.
- * @retval None
- */
- void AdcGrpRegularOverrunError_Callback(void)
- {
- /* Note: Disable ADC interruption that caused this error before entering in
- infinite loop below. */
- /* In case of error due to overrun: Disable ADC group regular overrun interruption */
- LL_ADC_DisableIT_OVR(ADC1);
- /* Error reporting */
- Error_Handler();
- }
复制代码
例程中没有输出变量,接下来的任务就是通过串口输出 ADC1 的值了。
根据板子的设计,将串口输出到 USART3_PD9,USART3_PD8。通过新建立的项目完成硬件配置,主要是将 ADC 的函数拷贝到项目中。- /**
- * @brief USART3 Initialization Function
- * @param None
- * @retval None
- */static void MX_USART3_UART_Init(void)
- {
- /* USER CODE BEGIN USART3_Init 0 */
- /* USER CODE END USART3_Init 0 */
- /* USER CODE BEGIN USART3_Init 1 */
- /* USER CODE END USART3_Init 1 */
- huart3.Instance = USART3;
- huart3.Init.BaudRate = 115200;
- huart3.Init.WordLength = UART_WORDLENGTH_8B;
- huart3.Init.StopBits = UART_STOPBITS_1;
- huart3.Init.Parity = UART_PARITY_NONE;
- huart3.Init.Mode = UART_MODE_TX_RX;
- huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
- huart3.Init.OverSampling = UART_OVERSAMPLING_16;
- huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
- huart3.Init.ClockPrescaler = UART_PRESCALER_DIV1;
- huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
- if (HAL_UART_Init(&huart3) != HAL_OK)
- {
- Error_Handler();
- }
- if (HAL_UARTEx_SetTxFifoThreshold(&huart3, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
- {
- Error_Handler();
- }
- if (HAL_UARTEx_SetRxFifoThreshold(&huart3, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
- {
- Error_Handler();
- }
- if (HAL_UARTEx_DisableFifoMode(&huart3) != HAL_OK)
- {
- Error_Handler();
- }
- /* USER CODE BEGIN USART3_Init 2 */
- /* USER CODE END USART3_Init 2 */
- }
复制代码
将例程拷贝到例程中,然后是 printf 函数的设置,main.c 中包含 #include <stdio.h>- #if defined(__ICCARM__)
- /* New definition from EWARM V9, compatible with EWARM8 */
- int iar_fputc(int ch);
- #define PUTCHAR_PROTOTYPE int putchar(int ch)
- #elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
- /* ARM Compiler 5/6*/
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
- #elif defined(__GNUC__)
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
- #endif /* __ICCARM__ */
- 还有就是声明 PUTCHAR_PROTOTYPE 宏。
- /* USER CODE BEGIN 4 */
- /**
- * @brief Retargets the C library printf function to the USART.
- * @param None
- * @retval None
- */
- PUTCHAR_PROTOTYPE
- {
- /* Place your implementation of fputc here */
- /* e.g. write a character to the USART1 and Loop until the end of transmission */
- HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
- return ch;
- }
- /* USER CODE END 4 */
复制代码
还需要一个 keil 的设置 MicroLib。
完成这些设置后就可以使用printf函数了。在主函数中, - printf("mVolt=%d \n",uhADCxConvertedData_Voltage_mVolt);
- while (1)
- {
- /* USER CODE END WHILE */
- /* Start ADC group regular conversion */
- if (HAL_ADC_Start_IT(&hadc1) != HAL_OK)
- {
- /* Error: ADC conversion start could not be performed */
- Error_Handler();
- }
- /* For this example purpose, wait until conversion is done */
- while (ubAdcGrpRegularUnitaryConvStatus != 1);
- printf("mVolt=%d \n",uhADCxConvertedData_Voltage_mVolt);
- /* Reset status variable of ADC group regular unitary conversion */
- ubAdcGrpRegularUnitaryConvStatus = 0;
- HAL_Delay(500);
- /* USER CODE BEGIN 3 */
- }
复制代码
下面是输出的结果
测试中我使用了我的一个宝贝,信号发生器。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() 函数。- /**
- * @brief The application entry point.
- * @retval int
- */
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- /* USER CODE END 1 */
- /* MCU Configuration--------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
- /* USER CODE BEGIN Init */
- /* USER CODE END Init */
- /* Configure the system clock */
- SystemClock_Config();
- /* USER CODE BEGIN SysInit */
- /* USER CODE END SysInit */
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_ICACHE_Init();
- MX_ETH_Init();
- MX_USART3_UART_Init();
- MX_RTC_Init();
- /* USER CODE BEGIN 2 */
- /* USER CODE END 2 */
- MX_ThreadX_Init();
- /* We should never get here as control is now taken by the scheduler */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
复制代码
可以看到程序非常简洁,微软的建议是将硬件的初始化尽可能的放在 main.C 中进行,这里放入 printf 的设置。不要放入太多的逻辑代码,应用的初始化尽可能在 tx_application_define 函数中进行。这里的初始化过程被封装到 status = MX_NetXDuo_Init(memory_ptr) 中了,在这个 MX_NetXDuo_Init 函数中在进行线程的初始化。主要有三个线程和一个回调函数。- /* Private function prototypes -----------------------------------------------*/
- static VOID App_Main_Thread_Entry (ULONG thread_input);
- static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);
- /* USER CODE BEGIN PFP */
- static VOID App_SNTP_Thread_Entry(ULONG thread_input);
- static VOID App_Link_Thread_Entry(ULONG thread_input);
复制代码
对我启发较大的是 App_Link_Thread_Entry 线程,这个线程会定期检测网络物理连接。
接下来是 App_Main_Thread_Entry 线程,这个线程主要是完成联网的准备工作。还有就是对 App_Link_Thread_Entry 线程数据的初始化。 - /* Exported macro ------------------------------------------------------------*/
- /* USER CODE BEGIN EM */
- #define PRINT_IP_ADDRESS(addr) do { \
- printf("STM32 %s: %lu.%lu.%lu.%lu \n", #addr, \
- (addr >> 24) & 0xff, \
- (addr >> 16) & 0xff, \
- (addr >> 8) & 0xff, \
- (addr & 0xff)); \
- } while(0)
复制代码
主要的工作线程是 App_SNTP_Thread_Entry,这个线程完成整个过程。 - /**
- * @brief SNTP thread entry.
- * @param thread_input: ULONG user argument used by the thread entry
- * @retval none
- */
- /* Define the client thread. */
- static void App_SNTP_Thread_Entry(ULONG info)
- {
- UINT ret;
- RtcHandle.Instance = RTC;
- ULONG seconds, fraction;
- ULONG events = 0;
- UINT server_status;
- NXD_ADDRESS sntp_server_ip;
- NX_PARAMETER_NOT_USED(info);
- sntp_server_ip.nxd_ip_version = 4;
- /* Create a DNS client */
- ret = dns_create(&DnsClient);
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* Look up SNTP Server address. */
- ret = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)SNTP_SERVER_NAME,
- &sntp_server_ip.nxd_ip_address.v4, NX_APP_DEFAULT_TIMEOUT);
- /* Check for error. */
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* Create the SNTP Client */
- ret = nx_sntp_client_create(&SntpClient, &NetXDuoEthIpInstance, iface_index, &NxAppPool, NULL, kiss_of_death_handler, NULL);
- /* Check for error. */
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* Setup time update callback function. */
- nx_sntp_client_set_time_update_notify(&SntpClient, time_update_callback);
- /* Use the IPv4 service to set up the Client and set the IPv4 SNTP server. */
- ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_ip.nxd_ip_address.v4);
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* Run whichever service the client is configured for. */
- ret = nx_sntp_client_run_unicast(&SntpClient);
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- else
- {
- PRINT_CNX_SUCC();
- }
- /* Wait for a server update event. */
- tx_event_flags_get(&SntpFlags, SNTP_UPDATE_EVENT, TX_OR_CLEAR, &events, PERIODIC_CHECK_INTERVAL);
- if (events == SNTP_UPDATE_EVENT)
- {
- /* Check for valid SNTP server status. */
- ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);
- if ((ret != NX_SUCCESS) || (server_status == NX_FALSE))
- {
- /* We do not have a valid update. */
- Error_Handler();
- }
- /* We have a valid update. Get the SNTP Client time. */
- ret = nx_sntp_client_get_local_time_extended(&SntpClient, &seconds, &fraction, NX_NULL, 0);
- ret = nx_sntp_client_utility_display_date_time(&SntpClient,buffer,64);
- if (ret != NX_SUCCESS)
- {
- printf("Internal error with getting local time 0x%x\n", ret);
- Error_Handler();
- }
- else
- {
- printf("\nSNTP update :\n");
- printf("%s\n\n",buffer);
- }
- }
- else
- {
- Error_Handler();
- }
- /* Set Current time from SNTP TO RTC */
- rtc_time_update(&SntpClient);
- /* We can stop the SNTP service if for example we think the SNTP server has stopped sending updates */
- ret = nx_sntp_client_stop(&SntpClient);
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* When done with the SNTP Client, we delete it */
- ret = nx_sntp_client_delete(&SntpClient);
- if (ret != NX_SUCCESS)
- {
- Error_Handler();
- }
- /* Toggling LED after a success Time update */
- while(1)
- {
- /* Display RTC time each second */
- display_rtc_time(&RtcHandle);
- HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
- /* Delay for 1s */
- tx_thread_sleep(100);
- }
- }
复制代码
这个程序目的是完成 SNTP 获取,设置到操作系统参数和 RTC 设置中,就可以不断地获取时间了。
可以看到时间显示出来了。但这里需要注意一个“坑”,就是发现时间好像和计算机显示的时间对不上,因为这个时间与UTC时间在不同时区,随即找到以下程序,- /* EPOCH_TIME_DIFF is equivalent to 70 years in sec
- calculated with www.epochconverter.com/date-difference
- This constant is used to delete difference between :
- Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
- time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF;
复制代码
将程序修改成: - /* EPOCH_TIME_DIFF is equivalent to 70 years in sec
- calculated with www.epochconverter.com/date-difference
- This constant is used to delete difference between :
- Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
- time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF + 28800U;
复制代码
这样转换成 UTC 0800 时区时间。
这次时间匹配成功。
后记
这个程序经长达3小时测试,未发现任何中断现象。反复的ping单片机地址,TTL 值一直很稳定,相比 lwIP 程序改善很多。
|