黄石网站建设方案,做网页代码的素材网站,中跃建设集团有限公司网站,深圳建设厅官网概述
应用中经常会有使用单片机进行模数转换的需求。PY32F003 具有 1 个 12 位的模拟数字转换器#xff08;ADC#xff09;#xff0c;今天我们一起来使用一下这个 ADC。
数据手册中对 ADC 简介如下。 SAR ADC#xff1a;逐次逼近式 ADC#xff0c;原理参见“参考链接ADC今天我们一起来使用一下这个 ADC。
数据手册中对 ADC 简介如下。 SAR ADC逐次逼近式 ADC原理参见“参考链接什么是SAR ADC - 知乎”。12位采样值的最大值4095。数据手册上标明的最大可用通道数量是 8 个外部通道但对照 PY32F003F18P 的管脚复用表如果应用中还要使用 GPIOLED定时器 和 UART 的话可使用的外部 ADC 通道数最多不超过 6 个。对比于 PY32F003F18P 的 20 脚封装和低廉的芯片价格这样的 MCU 可以在应用中采样 6 个外部模拟量通道也是相当可观的数量了。
PY32F003 可以在不使用外部晶振的情况下完成数模转换但其采样精度还需要验证。今天先尝试着把 ADC 的功能跑通先。
实现代码
参考在 STM32F103 上实现 ADC 的思路在 PY32F003 上完成一下看。大致的步骤如下
为 ADC1 指定 GPIO 管脚并设置其复用功能对 ADC1 进行初始化在主循环中进行采样和打印输出
在 main.h 中增加和 ADC 相关的函数声明
/** ----------------------------------------------------------------------------
* name : void ADC_Init(void)
* brief : ADC 初始化
* param : [in] None
* retval : [out] void
* remark :
*** ----------------------------------------------------------------------------
*/
void ADC_Init(void);/** ----------------------------------------------------------------------------
* name : HAL_StatusTypeDef ADC_Sample(char * sampleResult)
* brief : 获取 ADC 的采样结果结果存放在 sampleResult 字符串中
* param : [in] None
* retval : [out] HAL_HandleTypeDef. 操作成功返回 HAL_OK, 错误返回错误码。
* remark : sampleResult 是格式化的字符串需要解析
*** ----------------------------------------------------------------------------
*/
HAL_StatusTypeDef ADC_Sample(char * sampleResult);
在 app_adc.c 文件中实现函数功能
在 Application/User 组增加 app_adc.c 文件完整代码如下。
/********************************************************************************* file app_adc.c* brief Application level Analog-Digital Conveter codes.******************************************************************************* attention** Copyright (c) 2023 CuteModem Intelligence.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/#include main.hADC_HandleTypeDef hadc;
uint32_t adc_value[3];/********************************************************************************************************
* name : HAL_StatusTypeDef ADC_Sample(char * sampleResult)
* brief : 获取 ADC 的采样结果结果存放在 sampleResult 字符串中
* param : [in] None
* retval : [out] HAL_HandleTypeDef. 操作成功返回 HAL_OK, 错误返回错误码。
* remark : sampleResult 是格式化的字符串需要解析
********************************************************************************************************/
HAL_StatusTypeDef ADC_Sample(char * sampleResult)
{uint8_t i0;if(HAL_ADCEx_Calibration_Start(hadc) ! HAL_OK) return HAL_ERROR;HAL_ADC_Start(hadc); //开始采样for (i 0; i 3; i){HAL_ADC_PollForConversion(hadc, 10000); //等待ADC转换adc_value[i] HAL_ADC_GetValue(hadc); //获取AD值}#if(1)// excel formatsprintf(sampleResult, %d,%d,%d,(uint16_t)adc_value[0],(uint16_t)adc_value[1],(uint16_t)adc_value[2]);
#else// JSON formatsprintf(sampleResult, [{\C\:0,\D\:%d},{\C\:1,\D\:%d},{\C\:5,\D\:%d}],(uint16_t)adc_value[0],(uint16_t)adc_value[1],(uint16_t)adc_value[2]);
#endif HAL_ADC_Stop(hadc); // 停止采样return HAL_OK;
}void ADC_Init(void)
{ADC_ChannelConfTypeDef sConfig {0};__HAL_RCC_ADC_FORCE_RESET();__HAL_RCC_ADC_RELEASE_RESET();__HAL_RCC_ADC_CLK_ENABLE();hadc.Instance ADC1;if (HAL_ADCEx_Calibration_Start(hadc) ! HAL_OK) //AD校准Error_Handler();/* Configure global features of the ADC1 */hadc.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV1; //ADC_CLOCK_SYNC_PCLK_DIV2/4分频系数hadc.Init.Resolution ADC_RESOLUTION_12B; //设置采样位数hadc.Init.DataAlign ADC_DATAALIGN_RIGHT; //右对齐hadc.Init.ScanConvMode ADC_SCAN_DIRECTION_FORWARD; //扫描方向设置hadc.Init.EOCSelection ADC_EOC_SINGLE_CONV; //ADC_EOC_SINGLE_CONV:单次采样 ; ADC_EOC_SEQ_CONV:序列采样hadc.Init.LowPowerAutoWait ENABLE; //ENABLE读取ADC值后,开始下一次转换; DISABLE直接转换hadc.Init.ContinuousConvMode DISABLE; //ENABLE连续模式, DISABLE单次模式hadc.Init.DiscontinuousConvMode DISABLE; //非连续转换模式设置hadc.Init.ExternalTrigConv ADC_SOFTWARE_START; //触发模式设置hadc.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_NONE; //外部触发沿设置hadc.Init.DMAContinuousRequests DISABLE; //DMA连续模式设置hadc.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; //ADC_OVR_DATA_OVERWRITTEN过载时覆盖,ADC_OVR_DATA_PRESERVED保留旧值if (HAL_ADC_Init(hadc) ! HAL_OK) Error_Handler(); //初始化ADC/* Configure selected ADC channels */sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_RANK_CHANNEL_NUMBER; sConfig.SamplingTime ADC_SAMPLETIME_71CYCLES_5; if (HAL_ADC_ConfigChannel(hadc, sConfig) ! HAL_OK) Error_Handler();sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_RANK_CHANNEL_NUMBER; sConfig.SamplingTime ADC_SAMPLETIME_71CYCLES_5; if (HAL_ADC_ConfigChannel(hadc, sConfig) ! HAL_OK) Error_Handler();sConfig.Channel ADC_CHANNEL_4; sConfig.Rank ADC_RANK_CHANNEL_NUMBER; sConfig.SamplingTime ADC_SAMPLETIME_71CYCLES_5; if (HAL_ADC_ConfigChannel(hadc, sConfig) ! HAL_OK) Error_Handler();
}
在 app_adc.c 中定义了业务所需的变量功能函数也在一个 .c 文件中全部实现。这样做是参考了面向对象的编程模式遵循代码/变量和功能解耦的原则ADC 所需的全局变量都在 app_adc.c 中定义main.c 中就不用再引用 ADC 相关的变量也不用关心实现的细节了。唯一的接口就是 ADC_Sample() 函数的 sampleResultsampleResult 定义为一个字符串具有很好的通用性并隐藏了实现的细节。这里例子中被注释掉的 JSON 串返回结果的代码在实际应用中在上一层的业务逻辑处理是很方便的。当然 MCU 编程一般不会采用 JSON 这种富文本的格式这里只作为一种示例。
ADC_Sample() 函数中每次采样之前都对 ADC 进行了校准校准完成后开始采样采样完毕后停止 ADC。
在 py32f0xx_hal_msp.c 文件中指定 GPIO 及其复用功能
/*** -----------------------------------------------------------------------* name : void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)* brief : 初始化 ADC 相关 MSP* param : [in] *hadc, ADC handler pointer* retval : void* remark :* -----------------------------------------------------------------------
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{GPIO_InitTypeDef GPIO_InitStruct {0};/*PA0/1/4初始化*/if (hadc-Instance ADC1){__HAL_RCC_ADC_CLK_ENABLE(); /* Peripheral clock enable */__HAL_RCC_GPIOA_CLK_ENABLE(); /*ADC GPIO ConfigurationPA0 ------ ADC_IN0PA1 ------ ADC_IN1PA4 ------ ADC_IN5*/GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4; // 指定 PA0/1/4GPIO_InitStruct.Mode GPIO_MODE_ANALOG; // 设置为模拟端口GPIO_InitStruct.Pull GPIO_PULLDOWN; // 下拉无输入时采样值接近零HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 执行初始化}
}
按照厂家例程的文件组织所有的 HAL_xxx_MspInit() 集中在 py32_f0xx_hal_msp.c 文件中由于在 ADC_Init() 函数中调用了 HAL_ADC_Init() 函数要调用 HAL_ADC_MspInit()这个函数在 HAL 库中的原型是 weak 类型的并且是一个空函数因此需要在实用中重写。
当然把 HAL_ADC_MspInit() 函数在 app_adc.c 文件中实现也是可以的。
修改 DEBUG 口的管脚映射
PY32F003 ADC1 的通道 0/1/5 复用了 PA0/1/4之前的实验中PA1/0 被用作了 DEBUG 口 UART2和 ADC1 的通道是冲突的所以需要把 DEBUG 口对应的管脚挪走。查了数据手册AF4 组的 PA2/3 可以用作 UART2修改 UART_Config() 如下。
除了修改管脚映射以外中断优先级等的不做修改。
HAL_StatusTypeDef USART_Config(void)
{// Using PA2/PA3 (TX/RX)HAL_StatusTypeDef conf_res HAL_OK;GPIO_InitTypeDef GPIO_InitStruct;gUartInited 0; //// USART2初始化//__HAL_RCC_USART2_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();UartHandle.Instance USART2;UartHandle.Init.BaudRate 115200;UartHandle.Init.WordLength UART_WORDLENGTH_8B;UartHandle.Init.StopBits UART_STOPBITS_1;UartHandle.Init.Parity UART_PARITY_NONE;UartHandle.Init.HwFlowCtl UART_HWCONTROL_NONE;UartHandle.Init.Mode UART_MODE_TX_RX;conf_res HAL_UART_Init(UartHandle);if(conf_res ! HAL_OK) return conf_res;/**USART2 GPIO ConfigurationPA2 ------ USART2_TXPA3 ------ USART2_RX*/GPIO_InitStruct.Pin GPIO_PIN_2 | GPIO_PIN_3;GPIO_InitStruct.Mode GPIO_MODE_AF_PP;GPIO_InitStruct.Pull GPIO_PULLUP;GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate GPIO_AF4_USART2;HAL_GPIO_Init(GPIOA, GPIO_InitStruct);HAL_NVIC_SetPriority(USART2_IRQn, 0, 3); // 使能NVICHAL_NVIC_EnableIRQ(USART2_IRQn); // 使能USART2中断gUartInited 1;return conf_res;
}
在 main.c 的主循环中采样
int main(void)
{HAL_Init(); // systick初始化SystemClock_Config(); // 配置系统时钟GPIO_Config();if(USART_Config() ! HAL_OK) Error_Handler(); printf([SYS_INIT] Debug port initilaized.\r\n);ADC_Init();printf([SYS_INIT] ADC initilaized.\r\n);printf(\r\n---------------------------------------\r\n| PY32F003 MCU is ready. |\r\n---------------------------------------\r\n 10 digits sent to you! \r\n---------------------------------------\r\n);if (DBG_UART_Start() ! HAL_OK) Error_Handler();char sres[64]{0};uint8_t sIndex 0;while (1){ BSP_LED_Toggle(LED3);if(sIndex % 2 0){if(ADC_Sample(sres) HAL_OK){printf(%s\r\n, sres);}else{printf(Sample error.\r\n);}}sIndex ;}HAL_Delay(500);
}代码中主循环每 0.5s 翻转一次 LED每 1s 采样一次。
实验结果
初次跑通
按照上述步骤编写好代码编译烧录在 XCOM 上得到的结果如图。初次运行PA0/1/4 出于悬空状态得到的采样值是随机的。 注意到在 HAL_ADC_MspInt() 函数中将 PA0/1/4 这三个管脚的 PULL 属性都设置成了 PULLDOWN本想着即使悬空的话仍可得到接近 0 的采样值。但实验结果中PA0 的悬空状态采样值仍在 1480 多的值折合成电压为 1480/4096*3.3 1.192V
这个值挺高的而 PA1/4 管脚换算得到的电压值分别为 0.661/0.524V这两个值也不低。这说明 PY32F003 的内部下拉应该是“弱下拉”——或许在 HAL_ADC_Init() 函数中又对这几个管脚做了什么配置这个问题留着以后关注。
基于此在实际项目中用到 PY32F003 进行 ADC 时在信号管脚接入前要使用一个或一组运放做一下电压跟随才好。
采样时长
在 HAL_ADC_ConfigChannel() 中设置了采样周期均为 71.5加上转换的耗费 12.5 周期合计84 个时钟周期计算得到采样时间为 3.5us 一次也挺快了了。
对 GND 和 VCC 的采样值
将 PA0 接地然后再观察其采样值得到了全“0”的采样结果。
将 PA0 接 3.3V 管脚50次采样得到的平均值是 4087.22换算得到 3.293V也还好。
PULLUP 还是 PULLDOWN还是 NOPULL
把 PA0/1/4 都设置为内部上拉/下拉/无上下拉状态时PA0 接地测得 PA1/4 的采用值分别是
PULLUP2.159/2.191VPULLDOWN0.242/0.322VNOPULL1.990/3.061V
PA1和PA4的特性略有不同。
PA0 得到的采样值均为0这说明管脚的 PULL 被初始化的状态不会对采样的测量值产生影响。
在 PA0 接 VCC 时不论其 PULL 属性如何对采样值也没有影响。
总结
根据厂家例程移植跑通 ADC 的轮询式采样是比较简单的。如果熟悉对 STM32 的 ADC 配置可以照搬 STM32 的步骤。分配 ADC1 的采样通道时要把开发板默认的 UART2 管脚和 ADC1 的采样通道管脚错开。当某一管脚配置为模拟信号时其管脚的 PULL 属性对测量结果无影响。实用中ADC1 的采样输入管脚最好使用运放做一个电压跟随器。0~VCC 中间值的采样精度如何尚未验证留待后续实验完成。
后续还会继续尝试使用 DMA 的 ADC敬请期待。
谬误之处恳请指正。