510
个编辑
更改
→gyu_adc.c
此文件位于“05-光敏二极管实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。
此例程我们主要给大家展示STM32L4的ADC功能,所以我们宏定义中打开ADC相关的。 此例程我们只要展示的是外部GPIO中断,所以我们额外使能 HAL_GPIO_MODULE_ENABLED。另外为了辅助展示按键信息,我们额外添加了串口相关的DMA、UART这两个宏定义。<syntaxhighlight lang="c" line="1" start="103">
// 使能的宏
#define HAL_MODULE_ENABLED // 芯片
return ad_value; // 返回ADC采集值
}
</syntaxhighlight>
== 06-DAC实验 ==
此实验我们将会配置DAC1的通道1,作为模拟电压的输出引脚,并且配置一个ADC引脚,采集DAC输出的电压,并将电压值格式化打印到串口显示。
=== STM32L476 DAC简介 ===
DAC模块是一个12位电压输出数模转换器。 DAC可配置为8位或12位模式,并可与DMA控制器配合使用。在12位模式下,数据可以左对齐或右对齐。 DAC有两个输出通道,每个通道都有自己的转换器。在双DAC通道模式下,当两个通道组合在一起进行同步更新操作时,可以单独或同时完成转换。
DAC的主要特性如下:
•两个DAC转换器:每个转换器一个输出通道
•12位模式下的左或右数据对齐
•同步更新功能
•噪声波和三角波生成
•双DAC通道,用于独立或同步转换
•每个通道的DMA功能,包括DMA欠载错误检测
•转换的外部触发器
•DAC输出通道缓冲/非缓冲模式
•缓冲偏移校准
•每个DAC输出可以与DAC_OUTx输出引脚断开
•DAC输出连接到片上外设
•采样和保持模式,用于在停止模式下进行低功耗操作
•输入电压参考,VREF +
=== 硬件设计 ===
选择STM32L4引脚PA4作为模拟输出(DAC)引脚,配置PA7作为ADC引脚。
此实验测试的时候,由于选择的PA4及PA7引脚被显示屏占用,所以我们需要拔下显示屏,然后使用杜邦线短接PA4及PA7引脚。
[[文件:NBDK-SCH-DAC.png|边框|居中|无框|333x333像素]]
=== 实验准备 ===
# 使用miniUSB线及10pin排线,通过Jlink仿真器连接PC端和开发板。
# 使用miniUSB线,连接PC与开发板USB接口。
# 将SW1拨到DBG端,SW2拨到MCU。
# 使用Keil打开基础实验 06-DAC实验工程。
# 使用Xshell打开Jlink虚拟出的COM口
# 下载程序,并完成功能测试。
=== 实验验证 ===
下载完成后,打开COM口,可以看到每隔500ms打印一次采集到的ADC数据。
我们默认配置DAC引脚输出2000(也就是1.6V)电压,可以看到ADC采集到的数据为1.56V,这个是ADC采集的偏移量导致的。
[[文件:NBDK-XSHELL-DAC.png|边框|居中|无框|759x759像素]]
=== 源码详解 ===
本节中的源码说明,仅针对此例程中的重要功能,详细的源码介绍请大家参照代码后的注释。
==== stm32l4xx_hal_conf.h ====
此文件位于“06-光敏二极管实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。
此例程我们主要给大家展示STM32L4的DAC功能,所以我们宏定义中打开DAC相关的。<syntaxhighlight lang="c" line="1" start="103">
// 使能的宏
#define HAL_MODULE_ENABLED // 芯片
#define HAL_FLASH_MODULE_ENABLED // Flash
#define HAL_PWR_MODULE_ENABLED // 电源
#define HAL_RCC_MODULE_ENABLED // 时钟
#define HAL_CORTEX_MODULE_ENABLED // NVIC
#define HAL_GPIO_MODULE_ENABLED // GPIO
#define HAL_UART_MODULE_ENABLED // UART
#define HAL_DMA_MODULE_ENABLED // DMA
#define HAL_ADC_MODULE_ENABLED // ADC
#define HAL_DAC_MODULE_ENABLED // DAC
</syntaxhighlight>
==== main.c ====
main函数,我们的例程由此处开始执行,首先调用HAL_Init()函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。
接下来我们初始化了串口部分,目的是打印按键按下的调试信息。
接下来分别初始化了ADC以及DAC引脚配置。
在最后的while()循环中,我们设置DAC引脚的输出电压(默认设置输出1.6V),然后我们调用ADC值采集函数采集这个引脚电压,并且每隔500ms将采集值转换成电压值,格式化打印到串口显示。
<syntaxhighlight lang="c++" line="1" start="33">
int main(void)
{
uint16_t ad_value = 0;
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
// 重置所有外设、flash界面以及系统时钟
HAL_Init();
// 配置系统时钟(包含振荡器、系统时钟、总线时钟等等)
SystemClock_Config();
// 初始化串口USART1
MX_USART1_UART_Init();
// 初始化ADC1
MX_ADC1_Init();
// 初始化DAC1
MX_DAC1_Init();
//
while (1)
{
HAL_Delay(500);
HAL_DAC_Set(2000); // 设置2000DAC输出值(1.6V)
ad_value = HAL_ADC_Read(); // 获取ADC采集值
printf("adc = %d\r\n",ad_value); // 打印ADC采集值
printf("Adc_value = %.2fV\r\n",ad_value*3.3/4095); // 转换ADC采集为真实电压,并且打印到串口显示
}
}
</syntaxhighlight>
==== gyu_util.c ====
时钟初始化函数,用于配置我们模块运行的系统时钟、AHB高性能总线时钟、APB外设总线时钟以及单个外设的时钟。
主要包含了三个部分的初始化配置。
1.内部或者外部振荡器选择,也就是选择时钟信号的来源,是内部振荡,还是外部晶振。
2.时钟配置,选择系统、AHB总线及APB总线的时钟来源。
3.外设时钟配置,选择外设时钟来源。
为了给大家比较全面的展示各个时钟,我们振荡器选择HSI(内部16MHz高频)、HSE(外部8MHz高频)以及LSE(外部32.768KHz低频)三个。选择HSE作为PLL(锁相回路)时钟源,配置PLLCLK为80MHz。配置系统时钟SYSCLK、AHB高性能总线、APB外设总线(APB1及APB2)为80MHz。另外我们还分别配置了ADC、UART以及I2C的外设时钟。
基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。<syntaxhighlight lang="c++" line="1" start="49">
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct; // 定义RCC内部/外部振荡器结构体
RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义RCC系统,AHB和APB总线时钟配置结构体
RCC_PeriphCLKInitTypeDef PeriphClkInit; // 定义RCC扩展时钟结构体
// 配置LSE驱动器功能为低驱动能力
__HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
// 初始化CPU,AHB和APB总线时钟
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE
|RCC_OSCILLATORTYPE_LSE; // 设置需要配置的振荡器为HSI、HSE、LSE
// 配置HSE
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 激活HSE时钟(开发板外部为8MHz)
// 配置LSE
RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 激活LSE时钟(32.768KHz,低驱动)
// 配置HSI
RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 激活HSI时钟
RCC_OscInitStruct.HSICalibrationValue = 16; // 配置HSI为16MHz
// 配置PLL
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 选择HSE时钟作为PLL入口时钟源,8MHz
RCC_OscInitStruct.PLL.PLLM = 1; // 配置PLL VCO输入分频为1,8/1 = 8MHz
RCC_OscInitStruct.PLL.PLLN = 20; // 配置PLL VCO输入倍增为20,8MHz*20 = 160MHz
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI时钟7分频,160/7 = 22.857143MHz
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,160/2 = 80MHz
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 系统主时钟分区2分频,160/2 = 80MHz
// RCC时钟配置,出错则进入错误处理函数
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
// 初始化CPU,AHB和APB总线时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // 需要配置的时钟HCLK、SYSCLK、PCLK1、PCLK2
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 配置系统时钟为PLLCLK输入,80MHz
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟为系统时钟1分频,80/1 = 80MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1时钟为系统时钟1分频,80/1 = 80MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2时钟为系统时钟1分频,80/1 = 80MHz
// RCC时钟配置,出错则进入错误处理函数
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // HCLK=80MHz,Vcore=3.3V,所以选择SW4(FLASH_LATENCY_4)
{
_Error_Handler(__FILE__, __LINE__);
}
// 初始化外设时钟
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_USART2
|RCC_PERIPHCLK_LPUART1|RCC_PERIPHCLK_LPTIM1
|RCC_PERIPHCLK_I2C2|RCC_PERIPHCLK_ADC; // 需要初始化的外设时钟:USART1、USART2、LPUART1、LPTIM1、I2C2、ADC
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; // 配置串口USART1时钟为PCLK2,80MHz
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; // 配置串口USART2时钟为PCLK1,80MHz
PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI; // 配置LPUART时钟为HSI,16MHz
PeriphClkInit.I2c2ClockSelection = RCC_I2C2CLKSOURCE_PCLK1; // 配置I2C2时钟为PCLK1,80MHz
PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; // 配置LPTIM1时钟为LSE,32.768KHz
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1; // 配置ADC时钟为PLLSAI1,现在为80MHz,下面会重新定义
PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE; // 配置PLLSAI1时钟为HSE,8MHz
PeriphClkInit.PLLSAI1.PLLSAI1M = 1; // 配置PLLSAI1分频为1
PeriphClkInit.PLLSAI1.PLLSAI1N = 8; // 配置PLLSAI1倍增为8
PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7; // SAI时钟7分频,64/7 = 9.142857MHz
PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,64/2 = 32MHz
PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2; // 系统主时钟分区2分频,64/2 = 32MHz
PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK; // 配置PLLSAI1输出为ADC1时钟,也就是配置ADC1时钟,32MHz
// 外设时钟配置,出错则进入错误处理函数
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
// 配置内部主稳压器输出电压,配置为稳压器输出电压范围1模式,也就是:典型输出电压为1.2V,系统频率高达80MHz
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
// 配置系统定时器中断时间,配置为HCLK的千分频
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
// 配置系统定时器,配置为HCLK
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
// 系统定时器中断配置,设置系统定时器中断优先级最高(为0),且子优先级最高(为0)
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
</syntaxhighlight>
==== gyu_dac.c ====
DAC初始化函数,配置DAC1通道1(也就是PA4引脚)为DAC输出引脚。<syntaxhighlight lang="c++" line="1" start="37">
void MX_DAC1_Init(void)
{
hdac1.Instance = DAC1; // 配置为DAC1
if (HAL_DAC_Init(&hdac1) != HAL_OK) // 初始化DAC1
{
_Error_Handler(__FILE__, __LINE__);
}
// DAC通道配置结构体定义
DAC_ChannelConfTypeDef sConfig;
sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; //DAC模式
sConfig.DAC_Trigger=DAC_TRIGGER_NONE; // 不使用触发功能
sConfig.DAC_OutputBuffer=DAC_OUTPUTBUFFER_DISABLE; // DAC1输出缓冲关闭
sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; //不连接到片内外设
// 初始化通道CH1配置
if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
HAL_DAC_Start(&hdac1,DAC_CHANNEL_1); //开启DAC通道1
}
</syntaxhighlight>重新定义DAC的硬件引脚,可以看到,配置PA4为DAC引脚。<syntaxhighlight lang="c++" line="1" start="70">
void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hdac->Instance==DAC1) // 判断是否是DAC1
{
__HAL_RCC_DAC1_CLK_ENABLE(); // 使能DAC时钟
GPIO_InitStruct.Pin = GPIO_PIN_4; // 选择PA4
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;// 配置模拟模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化PA4引脚配置
}
}
</syntaxhighlight>设置DAC引脚输出电压,value为单元值,不是真正的电压值大小。
模拟电压值(范围:0~4095,对应0~3.3V)。<syntaxhighlight lang="c++" line="1" start="92">
void HAL_DAC_Set(uint16_t value)
{
HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,value); // 配置CH1 12位右对齐模拟输出
}
</syntaxhighlight>