ADC+DMA采集。RNG硬件随机数发生器。ADC的Vrefint Channel内部参照电压。FreeRTOS的Delay_us函数。这是能量机关其他部分的配置。
ADC+DMA采集
转换时间
ADC的工作流程为采样 - 比较 - 转换。采样是指对某一时刻的模拟电压进行采集,比较是指将采样的电压在比较电路中进行比较,转换是指将比较电路中结果转换成数字量
- ADC时钟
ADC输入时钟ADC_CLK,由PCLK2经过分频产生,最大值是36MHz。
- 采样周期
ADC需要若干个ADC_CLK周期完成对输入的电压进行采样。周期就是1/ADC_CLK。ADC最小需要3个周期,因为大幅击打不会有很高的频率,所以设置为480 Cycles也可以。
- 总转换时间
ADC的总转换时间 = (设置的采样周期数 + 12个周期)x ADC_CLK。加12个周期是因为转换精度设置为12Bits
ADC Parameter Settings
-
DMA Settings开启ADC。
- 配置下DMA模式为
Circular
,既循环更新数据。默认的Normal模式触发后只执行 一次。 - 设置方向Direction为从外设到内存。
- 配置自增地址为Memory方式,因为我程序里定义uint16_t 的数组来存储多路ADC数据,占两个字节所以选择
half word
- 配置下DMA模式为
-
Scan Conversion Mode 设置为
ENABLE
。多通道AD转换配置为使用扫描
-
Continuous Conversion Mode 设置为
ENABLE
。使能自动连续转换。DISABLE为单次转换,转换一次后停止需要代码手动控制才重新启动转换。
-
Discontinuous Conversion Mode 默认为
DISABLE
。关闭非连续转换模式,默认为DISABLE即可
-
DMA Continuous Requests 设置为
ENABLE
。每次转换完成后产生EOC的同时,也产生DMA请求
指定DMA以连续模式执行。(必须开启DMA才有效,且设置为ENABLE时必须配置DMA为circular模式)
-
End of Conversion Selection
每个通道转换结束后发送 EOC 标志/所有通道转换结束后发送EOC标志
-
Number Of Conversion 设置为
6
。配置几路通道采集及采集顺序。大幅一共5个扇叶加上一个 Vrefint Channel。下面的 RANK1-6 即可配置采集顺序和采样周期。
Vrefint Channel 内部参照电压
VREFINT是STM32的内部参照电压。一般来说STM32的ADC采用Vcc作为Vref,但为了防止Vcc存在波动较大导致Vref不稳定,进而导致采样值的比较结果不准确,STM32可以通过内部已有的参照电压VREFINT来进行校准,接着以VREFINT为参照来比较ADC的采样值,从而获得比较高的精度。VREFINT的电压为1.2V。Vrefint Channel在STM32内部完成没有对应引脚,只能出现在主ADC1中使用。
volatile fp32 voltage_vrefint_proportion = 8.0586080586080586080586080586081e-4f; // 初始化
adc_calibration = adc_original * (1.20f / voltage_vrefint_proportion);
程序
uint16_t ADC_ConvertedValue[6] = {0};
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_ConvertedValue, 6);
即可开启DMA传输。
ADC的DMA中断函数为DMA2_Stream0_IRQHandler
。可以直接在DMA中断函数内加入判断是否大幅被击打的程序部分。
最终实现的方法可以用很多种,比如用外部触发信号像是定时器之类,这里就是展示了其中一种的配置思路和方法。
不使用DMA的ADC单次采集
/**
* @brief 获取ADC采样值
* @retval none
* @example adcx_get_chx_value(&hadc1, ADC_CHANNEL_1);
*/
uint16_t adcx_get_chx_value(ADC_HandleTypeDef *ADCx, uint32_t ch)
{
static ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ch; // ADC转换通道
sConfig.Rank = 1; // ADC序列排序 即转换顺序
sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; // ADC采样时间
HAL_ADC_ConfigChannel(ADCx, &sConfig); // 设置ADC通道的各个属性值
// if (HAL_ADC_ConfigChannel(ADCx, &sConfig) != HAL_OK) {
// Error_Handler(); // Debug时使用
// }
HAL_ADC_Start(ADCx); // 开启ADC采样
HAL_ADC_PollForConversion(ADCx, 1); // 等待ADC转换结束
if (HAL_IS_BIT_SET(HAL_ADC_GetState(ADCx), HAL_ADC_STATE_REG_EOC)) { // 判断转换完成标志位是否设置
return (uint16_t)(HAL_ADC_GetValue(ADCx)); // 获取ADC值
} else {
return 1; // 发现数值为1代表错误
}
}
RNG硬件随机数发生器
STM32F4自带了RBG硬件随机数发生器,RNG处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个 32 位的随机数。
在CubeMX的Security使能RNG之后,还需要配置下时钟树的RNG专用时钟 (PLL48CLK) 。
生成的 MX_RNG_Init();
已经完成了所需的初始化。初始化的过程很简单,首先使能挂载到AHB2总线的随机数发生器时钟,然后使能随机数发生器即可完成初始化。
uint32_t HAL_RNG_GetRandomNumber(RNG_HandleTypeDef *hrng);
该函数用于读取随机数值。
每次读取之前,须先判断RNG_SR这个RNG 状态寄存器的DRDY最低位,如果该位为1则则说明RNG_DR这个数据寄存器的数据是有效的,可以读取得到随机数值,读取后该位自动清零;如果不为 1则需要等待。该函数已包含判断DRDY位并返回读取到的随机数值。
#include "stm32f4xx_hal_rng.h"
/**
* @brief 生成0-5随机数 用于确定大幅的亮灯顺序
*/
void RandGet(void)
{
uint8_t i = 0, j = 0, tmp = 0;
for(i = 0; i < 5; i ++) {
while (1) {
tmp = HAL_RNG_GetRandomNumber(&hrng) % 5;
for(j = 0; j < i; j ++) {
if(tmp == Rand[j]) {
break;
}
}
if(j == i) {
Rand[i] = tmp;
break;
}
}
}
}
// 如果是F1的板子没有RNG,可以换成伪随机数,用当前时间作为种子生成随机数
#include <stdlib.h>
// 时间作为种子
srand((unsigned int)HAL_GetTick());
tmp = rand() % 5;
HAL_Delay(1);
Delay_us
用FreeRTOS时,TimeBaseSource为SysTick以外的其他定时器,对应的us延时也需要改动。
/**
* @brief us延时
* @param[in] us
*/
#define TimebaseSource_is_SysTick 1
#if (!TimebaseSource_is_SysTick)
extern TIM_HandleTypeDef htim9; // 当使用FreeRTOS, TimebaseSource为其他定时器时
#define Timebase_htim htim9
#define Delay_GetCurrentValueReg() __HAL_TIM_GetCounter(&Timebase_htim)
#define Delay_GetReloadReg() __HAL_TIM_GetAutoreload(&Timebase_htim)
#else
#define Delay_GetCurrentValueReg() (SysTick->VAL)
#define Delay_GetReloadReg() (SysTick->LOAD)
#endif
static uint32_t fac_us = 0;
static uint32_t fac_ms = 0;
void Delay_us_init(void)
{
#if (!TimebaseSource_is_SysTick)
fac_ms = 1000000; // 作为时基的计数器时钟频率在HAL_InitTick()中被设为了1MHz
fac_us = fac_ms / 1000;
#else
fac_ms = SystemCoreClock / 1000;
fac_us = fac_ms / 1000;
#endif
}
void Delay_us(uint16_t us)
{
/*** 定时器功能实现 ***/
uint32_t ticks = us * fac_us;
uint32_t reload = Delay_GetReloadReg();
uint32_t told = Delay_GetCurrentValueReg();
uint32_t tnow = 0;
uint32_t tcnt = 0;
while (1) {
tnow = Delay_GetCurrentValueReg();
if (tnow != told) {
if (tnow < told) {
tcnt += told - tnow;
} else {
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks) {
break;
}
}
}
/*** NOP空语句实现 ***/
// uint32_t delay = us * fac_us / 4;
// do {
// __NOP();
// }
// while (delay --);
}