前言
前段时间被STM32H7的以太网和printf坑的不行, 昨天又被ADC坑了一下午, 这里怎么也得提笔记录一下了. 这次先来总结一下注意的地方:
- 开DCache
- ADC1的初始化不自动生成, 手动放到DMA1初始化后面, 数组定义到AXI SRAM, 如0x24000000
- ADC3的初始化不自动生成, 手动放到BDMA初始化后面, 数组定义到SRAM4, 如0x38000000
- 调
SCB_InvalidateDCache_by_Addr (void *addr, int32_t dsize)
使得数据更新
走通了流程, 解决了眼前困难, 留下一些问题, 接下来进入正文.
STM32CubeMX配置
这里只介绍ADC1+DMA1
和ADC3+BDMA
相关的配置
ADC时钟默认75MHz(可能为了xx对齐, 也有很多人调成72M)
ADC1用了四个通道
DMA1的数据宽度用半字, 因为16bit精度, 定义uint16_t
数组就可以了, 多了浪费, 所以这里是Half Word
. 如果非要定义成uint32_t
, 那DMA1这里的数据宽度就选Word
ADC3用了8个通道(7个外部+1个内部的温度传感器)
对应的BDMA数据宽度也是半字
开启DCache(牵涉到DMA的一般要开启DCache, 不然可能下载进去运行, 但是断电再上电就不运行的情况):
不生成初始化调用, 为了让ADC的初始化放到DMA初始化的后面
ADC DMA工作流程注意事项
stm32_h7xx_hal_adc.c
里面
(++) ADC conversion with transfer by DMA:
(+++) Activate the ADC peripheral and start conversions
using function HAL_ADC_Start_DMA()
(+++) Wait for ADC conversion completion by call of function
HAL_ADC_ConvCpltCallback() or HAL_ADC_ConvHalfCpltCallback()
(these functions must be implemented in user program)
(+++) Conversion results are automatically transferred by DMA into
destination variable address.
(+++) Stop conversion and disable the ADC peripheral
using function HAL_ADC_Stop_DMA()
STM32CUBE_FW_H7_V1.9.0
中的ADC_DMA_Transer
例子, readme.txt
中
@Note If the application is using the DTCM/ITCM memories (@0x20000000/ 0x0000000: not cacheable and only accessible
by the Cortex M7 and the MDMA), no need for cache maintenance when the Cortex M7 and the MDMA access these RAMs.
If the application needs to use DMA(or other masters) based access or requires more RAM, then the user has to:
- Use a non TCM SRAM. (example : D1 AXI-SRAM @ 0x24000000)
- Add a cache maintenance mechanism to ensure the cache coherence between CPU and other masters(DMAs,DMA2D,LTDC,MDMA).
- The addresses and the size of cacheable buffers (shared between CPU and other masters)
must be properly defined to be aligned to L1-CACHE line size (32 bytes).
相关代码和测试结果
先是定义到指定地址的数组, 为什么下面再解释
#define ADC1_BUFFER_SIZE 32*4 //ADC1用了4通道, 存32组, 方便做平均
#define ADC3_BUFFER_SIZE 32*8 //ADC3用了8通道, 存32组, 方便做平均
//32字节对齐(地址+大小)
//adc1_data指定到 AXI SRAM 的0x24000000
//adc3_data指定到 SRAM4 的0x38000000
ALIGN_32BYTES (uint16_t adc1_data[ADC1_BUFFER_SIZE]) __attribute__((section(".ARM.__at_0x24000000")));
ALIGN_32BYTES (uint16_t adc3_data[ADC3_BUFFER_SIZE]) __attribute__((section(".ARM.__at_0x38000000")));
ADC的初始化
void adc_init(void)
{
MX_ADC1_Init(); //初始化调用放这里, 确保在MX_DMA_Init()初始化后面
MX_ADC3_Init(); //在MX_BDMA_Init()初始化后面
//HAL_Delay(100); //有地方说这里可以等等电压稳定后再校准
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
printf("hadc1 error with HAL_ADCEx_Calibration_Start\r\n");
Error_Handler();
}
if (HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
printf("hadc3 error with HAL_ADCEx_Calibration_Start\r\n");
Error_Handler();
}
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc1_data, ADC1_BUFFER_SIZE) != HAL_OK)
{
printf("hadc1 error with HAL_ADC_Start_DMA\r\n");
Error_Handler();
}
if (HAL_ADC_Start_DMA(&hadc3, (uint32_t *)adc3_data, ADC3_BUFFER_SIZE) != HAL_OK)
{
printf("hadc3 error with HAL_ADC_Start_DMA\r\n");
Error_Handler();
}
}
类似ping-pong存储
ADC转换半满中断中把数据存到数组的前半部分?
ADC转换完成中断中把数据存到数组的后半部分?
void SCB_InvalidateDCache_by_Addr (void *addr, int32_t dsize)
中dsize
是字节数, 所以ADC1_BUFFER_SIZE
刚好是uint16_t
类型adc1_data
数组一半的容量, 这点要特别注意
如果嫌中断太频繁, 可以把SCB_InvalidateDCache_by_Addr((uint32_t *)adc1_data, sizeof(adc1_data));
放到读取之前.
【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样 里面解释了数据一致性的问题
由于STM32H7 Cache的存在,凡是CPU和DMA都会操作到的存储器,我们都要注意数据一致性问题。对于本章节要实现的功能,要注意读Cache问题,防止DMA已经更新了缓冲区的数据,而我们读取的却是Cache里面缓存的。这里提供两种解决办法:
- 方法一: 关闭DMA所使用SRAM存储区(MPU操作的)。
- 方法二: 设置SRAM的缓冲区做32字节对齐,大小最好也是32字节整数倍,然后调用函数SCB_InvalidateDCache_by_Addr做无效化操作即可,保证CPU读取到的数据是刚更新好的。
由于CubeMX里面开了DCache, 但没有用MPU, 就用第二种方法
uint32_t flag0 = 0;
uint32_t flag1 = 0;
uint32_t flag2 = 0;
uint32_t flag3 = 0;
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
/* Invalidate Data Cache to get the updated content of the SRAM on the first half of the ADC converted data buffer */
if(hadc->Instance == ADC1) {
flag0++;
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc1_data[0], ADC1_BUFFER_SIZE);
} else if(hadc->Instance == ADC3) {
flag1++;
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc3_data[0], ADC3_BUFFER_SIZE);
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
/* Invalidate Data Cache to get the updated content of the SRAM on the second half of the ADC converted data buffer */
if(hadc->Instance == ADC1) {
flag2++;
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc1_data[ADC1_BUFFER_SIZE/2], ADC1_BUFFER_SIZE);
} else if(hadc->Instance == ADC3) {
flag3++;
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc3_data[ADC3_BUFFER_SIZE/2], ADC3_BUFFER_SIZE);
}
}
看看1s到底转换了多少次, 把结果和温度打印出来
void soft_timer_1000ms_callback(void)
{
if(flag0) {
printf("flag0 = %d\r\n", flag0);
flag0 = 0;
}
if(flag1) {
printf("flag1 = %d\r\n", flag1);
flag1 = 0;
}
if(flag2) {
printf("flag2 = %d\r\n", flag2);
flag2 = 0;
}
if(flag3) {
printf("flag3 = %d\r\n", flag3);
flag3 = 0;
}
printf("\r\n");
//温度计算
uint32_t TS_DATA = 0;
for(uint32_t i = 7; i < ADC3_BUFFER_SIZE; i+=8) {
TS_DATA += adc3_data[i];
}
TS_DATA /= 32; //32组数据求平均
uint16_t TS_CAL1 = *(__IO uint16_t *)(0x1FF1E820);
uint16_t TS_CAL2 = *(__IO uint16_t *)(0x1FF1E840);
float temperature = (110.0-30.0)/(TS_CAL2-TS_CAL1) * (TS_DATA - TS_CAL1) + 30.0;
printf("temperature = %d, %.2f\r\n", TS_DATA, temperature);
//这里没有考虑ping-pong转换, 直接全取了, 新旧数据差别不是太大?
printf("\r\n+++++++++++++++++++++++++++++++++++++++\r\n");
for(uint32_t i = 0; i < ADC1_BUFFER_SIZE; i++) {
if(i % 4 == 0) {
printf("\r\n%d ", i/4);
}
printf("%d, ",adc1_data[i]);
}
printf("\r\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\r\n");
for(uint32_t i = 0; i < ADC3_BUFFER_SIZE; i++) {
if(i % 8 == 0) {
printf("\r\n%d ", i/8);
}
printf("%d, ", adc3_data[i]);
}
printf("\r\n");
}
结果如下, 两组
- ADC1(PA6, PB1, PB0, PC0)
- ADC3(PF9, PF7, PF5, PF3, PF8, PF6, PF4, TSensor)
PB1, PF6接GND, PB0接3V3, 温度传感器报出来的温度是41.86℃
内存分配
用户手册里面
先来看内存的地址
再来看总线联系
图中画X
的是可以访问的:
DMA1-MEM
访问不了默认的DTCM
(0x20000000开始), 但可以访问AXI SRAM
(0x24000000)BDMA
只能访问SRAM4
(0x38000000开始)
关于SRAM的说明里面也明确指明了这一点, 如以太网和USB只能访问SRAM3(0x30040000):
系统架构图也有说明:
采样率计算
按24MHz, 整周期对齐可能计算比较准确, 这里要求不高, 有数就行
# ADC时钟源75MHz
# 6分频后 75/6=12.5MHz, 这就是ADC Clock
# 16bit分辨率
# 转换时间 = 采样时间 + 逐次逼近时间
# TCONV = Sampling time + TSAR = 64.5 + 8.5 = 73 ADC clock cycles
# 转换频率 = 12.5M / 73 = 171.233KHz
# 转换周期 = 1 / 171.233kHz = 5.84us
# 334和668的计算?
# 5.84 * 32 * 8 * 334 = ? 少一个 * 2
8.5是24MHz下, 16bit分辨率对应的TSAR, 如下, 不太清楚对12.5MHz是怎样的, 所以可以调整时钟树, 让时钟源72MHz, 这样好算一点
内部温度传感器
内部温度传感器的测量范围在-40~125℃
,
在datasheet里面
参考
如下
- stm32h743 datasheet
- stm32h743 usermanual
- 【STM32H7教程】第44章 STM32H7的ADC基础知识和HAL库API
- 【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样
- STM32H743+CubeMX-ADC+DMA采样四路AD
- STM32H743关于ADC与DMA不工作问题
- STM32H743Nucleo ADC使用DMA配置无法读取数据的问题及解决
- stm32H7 CubeMX 配置ADC DMA 目标数组无值更新
欢迎扫描二维码关注本人微信公众号, 及时获取最新文章: