STM32CubeMX 开发笔记
软件环境:
- STM32CubeMX 6.4.0
- Clion 2021.3.2
- gcc-arm-none-eabi-10.3-2021.10
- CMake 3.21.1
- MinGW 5.4
硬件环境:
STM32L432KBU6自制PCB
ADC DMA Oversampling
如何使用ADC采样?
第一次选择的时候就是看中了。STM32L432的16位ADC,参考他的系统框图,你可以看到16位ADC。
其实不是真的16位ADC,翻翻手册发现实际上是12ADC从采样到16位使用:
更气人的是Cubemx还单独列出了他:
您选择保存后生成代码:
其实库里根本没有16位ADC宏定义。
FAILED: CMakeFiles/ELE.elf.dir/Core/Src/adc.c.obj D:\Embedded\STM32\arm_gcc\gcc-arm-none-eabi-10.3-2021.10\bin\arm-none-eabi-gcc.exe -DSTM32L432xx -DUSE_HAL_DRIVER -ID:/Embedded/STM32/Project/ELE/Core/Inc -ID:/Embedded/STM32/Project/ELE/Drivers/STM32L4xx_HAL_Driver/Inc -ID:/Embedded/STM32/Project/ELE/Drivers/STM32L4xx_HAL_Driver/Inc/Legacy -ID:/Embedded/STM32/Project/ELE/Drivers/CMSIS/Device/ST/STM32L4xx/Include -ID:/Embedded/STM32/Project/ELE/Drivers/CMSIS/Include -g -mcpu=cortex-m4 -mthumb -mthumb-interwork -ffunction-sections -fdata-sections -fno-common -fmessage-length=0 -Og -g -std=gnu11 -MD -MT CMakeFiles/ELE.elf.dir/Core/Src/adc.c.obj -MF CMakeFiles\ELE.elf.dir\Core\Src\adc.c.obj.d -o CMakeFiles/ELE.elf.dir/Core/Src/adc.c.obj -c D:/Embedded/STM32/Project/ELE/Core/Src/adc.c D:/Embedded/STM32/Project/ELE/Core/Src/adc.c: In function 'MX_ADC1_Init': D:/Embedded/STM32/Project/ELE/Core/Src/adc.c:47:27: error: 'ADC_RESOLUTION_16B' undeclared (first use in this function); did you mean 'ADC_RESOLUTION_6B'? 47 | hadc1.Init.Resolution = ADC_RESOLUTION_16B; | ^~~~~~~~~~~~~~~~~~ | ADC_RESOLUTION_6B
我别无选择,只能用采样来尝试,这里记录一下解决方案供大家参考:
首先说一下CubeMX如何配置:
引脚分布如下:
ADC配置如下:
我不会重复其他具体选项的含义。如果需要,可以看:ADC配置信息。
以下是关于采样的配置信息:
- Oersampling Right Shift:过采样右移位数
此处随着过采样倍数而变化,本文使用的是256倍也就是降采样信息扩大了2^8倍,这样就相当于ADC读到的数据全部左移了8位,但是ADC_DR最大支持16位,所以要再次右移4位。
- Oversampling Ratio:过采样倍数
根据需要的位数进行倍变。
- Regular Oversamping Mode:规则通道过采样模式
在注入序列中断时,超采样会暂时停止或重置。如果在常规组和注入组上都启用了超采样,这个参数将被丢弃并强制设置为"ADC_REGOVERSAMPLING_RESUMED_MODE"(在注入序列中,超采样缓冲器被清零)。本文没有用到注入通道所以选择连续模式。
- Triggered Regular Oversampling:过采样触发模式 这个很好理解,多通道时是一次性全部触发还是一个一个接着触发采样。
关于过采样的原理这里就不再详细介绍,具体可见:过采样原理。
可以看到硬件上完成了过采样操作,直接输出16位的数据到寄存器。
DMA 的配置
因为使能了连续采样模式,所以DMA要开启来配合连续读入:
单次模式执行(当达到转换次数时,DMA传输停止)还是以连续模式执行(无论转换次数多少,DMA传输无限制)。
注意:在连续模式下,DMA必须被配置为循环模式。否则,当达到DMA缓冲区的最大指针时,就会触发超限。
在使能 DMA 模式的情况下(ADC_CR2 寄存器中的 DMA 位置 1),每完成规则通道组中的 一个通道转换后,都会生成一个 DMA 请求。这样便可将转换的数据从 ADC_DR 寄存器传输 到用软件选择的目标位。
注意这里使用的是Word,也就是按字来递增。理论上来说这里变换为short(halfword)类型也是可以的。
代码编写
首先使用的时候要校准一下ADC,然后开启一次DMA转换即可因为开启了ADC连续模式并且开启了DMA,
校准ADC的API:
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
开启ADC DMA传输的API:
if ( HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,1) != HAL_OK)
{
Error_Handler();
}
最后我们使用串口把数据传输至PC:
可以看到浮空电压不是很稳定,我们测量3.3V试一试:
LDO使用的TI的超低噪声产品,看到电压测量很稳定的:
完整的代码如下:
/* USER CODE BEGIN 2 */
volatile uint32_t ADC_Value=0;
char TX_Uart_Buffer[30];
memset(TX_Uart_Buffer,0,sizeof (TX_Uart_Buffer));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
if ( HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,1) != HAL_OK)
{
Error_Handler();
}
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(500);
sprintf(TX_Uart_Buffer , "ADC VALUE IS %lu\r\n", ADC_Value);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)TX_Uart_Buffer, sizeof (TX_Uart_Buffer));
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}
/* USER CODE END 3 */
一些小问题
首先是串口写的时候有点小问题:
可以看到输出的时候稳定会出现一些字符,出现还很稳定:
I?
很离谱,刚开始没想到,想了一会就想起来了,是初始化的时候没有清空,或者说发送的时候选择长度函数不太对:
实际上应该这样写:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)TX_Uart_Buffer, strlen(TX_Uart_Buffer));
原来的写法:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)TX_Uart_Buffer, sizeof (TX_Uart_Buffer));
sizeof返回的是char组的长度,可能是写kotlin习惯了直接sizeof,这次就用了sizeof导致没有初始化的部分露出来了,这样写也不是不行,你可以这样:
使用前先初始化一下:
memset(TX_Uart_Buffer,0,sizeof (TX_Uart_Buffer));
ADC DMA的问题
实际上这是一个很小的问题,就是ADC获取到的数据被DMA传输过来以后直接丢到了对应的位置,但是存在一种可能就是,DMA传输了一半数据,这时候被另一个DMA通道传去了串口,造成剩下的一半实际上是上一个数据,所以还是推荐使用单次触发来测量数据,或者增大数据缓冲区来解决冲突访问的问题。
最后的写法:
/* USER CODE BEGIN 2 */
volatile uint32_t ADC_Value=0;
char TX_Uart_Buffer[30];
memset(TX_Uart_Buffer,0,sizeof (TX_Uart_Buffer));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(500);
if ( HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,1) != HAL_OK)
{
Error_Handler();
}
sprintf(TX_Uart_Buffer , "ADC VALUE IS %lu\r\n", ADC_Value);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)TX_Uart_Buffer, strlen(TX_Uart_Buffer));
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}