-
基于循环DMA+PWM实现正玄波
2019-07-11 19:20:11DMA_ITConfig(DMA1_Channel5,DMA_IT_TC|DMA_IT_HT|DMA_IT_TE,DISABLE); do{ DMA_InitTypeDef tDMA_InitlStructure; DMA_StructInit( &tDMA_InitlStructure ); switch(chSelectChannel){ case 1: tDMA_...最近看群里大家在讨论PWM,想起以前写的PWM模拟正玄波的代码,拿出来分享下。
其实也很简单,我就把我做的思路讲解下,期望能帮助到用到的人吧。
首先说下要求:
1、16点的正玄波,当然这个是不是重点;
2、正玄波周期可调;
3、正玄波幅度可调;
实现思路:
一、利用matlab仿真,计算出16点的正玄波基础数据
static const float c_fSinStandardValue[]={ 0.0,631.4276634 ,1166.726189 ,1524.401229 ,1650 ,1524.401229 ,1166.726189 ,631.4276634 , 0.0,-631.4276634,-1166.726189,-1524.401229,-1650,-1524.401229,-1166.726189,-631.4276634, };
这16个数据点适合周期无关的。
二、怎么显示负半波
由于我们的板子没有负压,因此我要想实现输出负压,只能提供直流分量,因此我在算法里面把直流分量
提高到了1.65V。
三、根据用户输入频率和幅度计算周期和占空比
static bool calculate_pwm_ratio(uint16_t hwSelectSinV_mV,uint32_t wSelectSinF_Hz) { uint8_t i=0; uint16_t hwPwmT=0; if(!wSelectSinF_Hz){ return false; } hwPwmT = TIMX_CLK/(wSelectSinF_Hz*DMA_BUFFER_SIZE); for(i=0;i<DMA_BUFFER_SIZE;i++){ s_hwDmaBuffer[i] = (uint16_t)(((hwSelectSinV_mV/3300.0)*c_fSinStandardValue[i]+1650.0)*hwPwmT/3300.0); if(s_hwDmaBuffer[i]>hwPwmT){ s_hwDmaBuffer[i]=hwPwmT; } if(s_hwDmaBuffer[i]){ s_hwDmaBuffer[i] -=1; } } return true; }
四、就是底层硬件驱动了
DMA驱动:
static bool pwm_dma(uint8_t chSelectChannel) { DMA_Cmd(DMA1_Channel5,DISABLE); DMA_DeInit(DMA1_Channel7); DMA_ITConfig(DMA1_Channel5,DMA_IT_TC|DMA_IT_HT|DMA_IT_TE,DISABLE); do{ DMA_InitTypeDef tDMA_InitlStructure; DMA_StructInit( &tDMA_InitlStructure ); switch(chSelectChannel){ case 1: tDMA_InitlStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM4->CCR1; break; case 2: tDMA_InitlStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM4->CCR2; break; case 3: tDMA_InitlStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM4->CCR3; break; default: return false; } tDMA_InitlStructure.DMA_MemoryBaseAddr = (uint32_t)s_hwDmaBuffer; tDMA_InitlStructure.DMA_DIR = DMA_DIR_PeripheralDST; tDMA_InitlStructure.DMA_BufferSize = DMA_BUFFER_SIZE; tDMA_InitlStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; tDMA_InitlStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; tDMA_InitlStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; tDMA_InitlStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; tDMA_InitlStructure.DMA_Mode = DMA_Mode_Circular; tDMA_InitlStructure.DMA_Priority = DMA_Priority_High; tDMA_InitlStructure.DMA_M2M = DMA_M2M_Disable; DMA_ClearFlag( DMA1_IT_TC7 ); DMA_Init(DMA1_Channel7, &tDMA_InitlStructure); }while(0); DMA_Cmd(DMA1_Channel7,ENABLE); return true; }
IO口驱动:
void user_pwm_init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); do{ static const GPIO_InitTypeDef c_tGPIO_InitStructure={ .GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_7 | GPIO_Pin_6, .GPIO_Mode = GPIO_Mode_AF_PP, .GPIO_Speed = GPIO_Speed_50MHz, }; GPIO_Init(GPIOB, (GPIO_InitTypeDef*)&c_tGPIO_InitStructure); }while(0); //NVIC_SetPriority(DMA1_Channel5_IRQn, 0); //NVIC_EnableIRQ(DMA1_Channel5_IRQn); }
PWM驱动:
bool user_pwm_cfg(uint16_t hwSetV_mV,uint16_t hwSetF_Hz,uint8_t chSelectChannel) { //GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM_OCInitTypeDef TIM_OCInitStructure; TIM_Cmd(TIM4, DISABLE); if(!hwSetF_Hz){ return false; } if(!pwm_dma(chSelectChannel)){ return false; } if(!calculate_pwm_ratio(hwSetV_mV,hwSetF_Hz)){ return false; } /* 第1步:打开GPIOB RCC_APB2Periph_AFIO 的时钟 */ //RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); /* do{ static const GPIO_InitTypeDef c_tGPIO_InitStructure={ .GPIO_Pin = GPIO_Pin_6 |GPIO_Pin_7, .GPIO_Mode = GPIO_Mode_Out_PP, .GPIO_Speed = GPIO_Speed_50MHz }; GPIO_Init(GPIOB, (GPIO_InitTypeDef*)&c_tGPIO_InitStructure); GPIO_ResetBits(GPIOB, GPIO_Pin_6); GPIO_ResetBits(GPIOB, GPIO_Pin_7); }while(0); */ /* 配置GPIO为复用推挽输出模式 */ /* do{ static const GPIO_InitTypeDef c_tGPIO_InitStructure={ .GPIO_Pin = GPIO_Pin_8, .GPIO_Mode = GPIO_Mode_AF_PP, .GPIO_Speed = GPIO_Speed_50MHz, }; GPIO_Init(GPIOB, (GPIO_InitTypeDef*)&c_tGPIO_InitStructure); }while(0); */ /* 使能TIM4的时钟 */ //RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); do{ hwSetF_Hz = TIMX_CLK/(hwSetF_Hz*DMA_BUFFER_SIZE); if(hwSetF_Hz<=1){ return false; } TIM_TimeBaseStructure.TIM_Period = hwSetF_Hz - 1; /* TIM_Period = TIM3 ARR Register */ TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); }while(0); /* PWM1 Mode configuration: Channel1 */ do{ TIM_OCInitTypeDef tTIM_OCInitStructure={ .TIM_OCMode = TIM_OCMode_PWM1, .TIM_OutputState = TIM_OutputState_Enable, //.TIM_Pulse = (TIM_TimeBaseStructure.TIM_Period * hwSetV_mV) / SYS_PWM_MAX_V, /* 改变占空比 */ .TIM_Pulse =0, /* 改变占空比 */ .TIM_OCPolarity = TIM_OCPolarity_High }; TIM_OC1Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Disable); TIM_OC2Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Disable); TIM_OC3Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Disable); TIM_OC4Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Disable); switch(chSelectChannel){ case 1: //TIM_OC1Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); break; case 2: //TIM_OC2Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); break; case 3: //TIM_OC3Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); break; default: return false; } //TIM_OC4Init(TIM4, (TIM_OCInitTypeDef*)&tTIM_OCInitStructure); //TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Disable); }while(0); TIM_ARRPreloadConfig(TIM4, ENABLE); //DMA do{ //TIM_DMAConfig(TIM4,TIM_DMABase_CCR3,TIM_DMABurstLength_2Bytes); TIM_DMACmd(TIM4,TIM_DMA_Update,ENABLE); }while(0); //DMA_Cmd(DMA1_Channel5,ENABLE); /* TIM4 enable counter */ TIM_Cmd(TIM4, ENABLE); TIM_CtrlPWMOutputs(TIM4, ENABLE); return true; }
输出波形,经过一个低通滤波器后,输出了正玄波。
源码下载地址:https://download.csdn.net/download/wuhenyouyuyouyu/11339564
-
ADC-dma死循环问题
2021-01-26 12:18:06初始化dma时候,里面会先使能dma,然后打开相应的中断,然后结果会导致main函数中卡死 卡在箭头处,也就是打不开adc和dma 解决方案 全部注释,只留第一个配置dma的函数展开全文 -
hal库串口dma卡死_HAL库版DMA循环模式串口数据收发
2020-12-23 02:44:43STM32CubeMX生成的HAL库中,提供了三类串口数据收发的接口,分别为阻塞模式,非阻塞模式和DMA模式,文本主要对DMA模式进行了分析并依据提供的接口提出了更加实用的串口数据收发方案。通过对网上资料的查找和分析,...在《STM32CubeMX初识与工程创建》的基础上,首先对串口进行设置,以实现通过串口对数据的收发。STM32CubeMX生成的HAL库中,提供了三类串口数据收发的接口,分别为阻塞模式,非阻塞模式和DMA模式,文本主要对DMA模式进行了分析并依据提供的接口提出了更加实用的串口数据收发方案。通过对网上资料的查找和分析,主要存在下述两个问题:
1、在数据接收过程中,采用了串口的空闲中断实现了DMA模式下不定长数据的接收,但存在的限制是单次接收的数据长度必须小于DMA缓冲区的长度,如果接收的数据长度大于DMA缓冲区的长度,就数据丢失了。
2、在数据发送过程中,DMA在发送阶段是不能再使能DMA发送的,即调用HAL库中的HAL_UART_Transmit_DMA接口后,在DMA传输数据完成之前是不能再调用该接口的,常用的方式是通过一个标志变量来确定是否可发送,但程序中若存在等待标志变量等逻辑的话,会降低程序运行的效率。
本文通过采用DMA的循环模式配合DMA传输完成中断和串口空闲中断解决DMA缓冲区长度对串口接收数据量的限制问题。采用类递归的逻辑解决串口发送数据等待发送完成标志的问题。
1 硬件配置
首先,应用STM32CubeMX对串口进行配置,
在Connectivity中勾选usart1,具体引脚根据硬件确定。重点注意的是,勾选USART1 global interrupt 使能;将收发的DMA添加上,将USART1_RX的DMA设置为Circular模式,将USART1_TX的DMA设置为Normal模式。
2 软件实现方案
2.1 初始化准备
将程序内部做驱动层和应用层的区分,串口数据收发接口的封装属于驱动层面,应用层面调用驱动层的接口实现数据的发送和获取,通过收发两个缓冲区实现数据的交互,本文中采用一组循环FIFO实现数据的缓冲。新建一组文件提供对串口收发数据的中间件接口,在头文件中定义串口中间件属性:
typedef struct
{
UART_HandleTypeDef *handle; /*HAL库提供的串口句柄*/
int16_t TransFlag; /*数据发送标志位*/
int32_t DmaSize; /*DMA缓冲区的大小*/
int32_t DamOffset; /*获取数据在DMA缓冲区的偏移量*/
uint8_t *pReadDma; /*指向接收DMA缓冲区的首地址*/
uint8_t *pWriteDma; /*指向发送DMA缓冲区的首地址*/
CFIFO ReadCFifo; /*接受数据的循环缓冲区*/
CFIFO WriteCFifo; /*发送数据的循环缓冲区*/
}MW_UART_ATTR;
上述属性值中包括了缓冲区,DMA等参数,后续实现中具体讲述各参数的用途。本文仅提供对一个串口进行配置用作演示,实际中在属性中添加了id参数实现多串口的管理,或者采用其他方式。
#define MW_TRANS_IDLE 0
#define MW_TRANS_BUSY 1
#define MW_UART_BUFFER_LEN 1024
#define MW_UART_DMA_LEN 256
static uint8_t Uart1TxBuff[MW_UART_BUFFER_LEN] = {0};
static uint8_t Uart1RxBuff[MW_UART_BUFFER_LEN] = {0};
static uint8_t Uart1TxDma[MW_UART_DMA_LEN] = {0};
static uint8_t Uart1RxDma[MW_UART_DMA_LEN] = {0};
static MX_UART_ATTR sUartAttr;
int8_t MW_UART_Init(UART_HandleTypeDef *handle)
{
/*为属性的参数附初值*/
MX_UART_ATTR *pUartAttr = &sUartAttr;
pUartAttr->handle = handle;
pUartAttr->DamOffset = 0;
pUartAttr->TransFlag = MW_TRANS_IDLE;
pUartAttr->DmaSize = MW_UART_DMA_LEN;
pUartAttr->pReadDma = Uart1RxDma;
pUartAttr->pWriteDma = Uart1TxDma;
CFIFO_Init(&pUartAttr->ReadCFifo, Uart1RxBuff, MW_UART_BUFFER_LEN);
CFIFO_Init(&pUartAttr->WriteCFifo, Uart1TxBuff, MW_UART_BUFFER_LEN);
/*配置DMA参数并使能中断*/
if(HAL_OK != HAL_UART_Receive_DMA(pUartAttr->handle, pUartAttr->pReadDma, MW_UART_DMA_LEN))
{
return MW_FAIL;
}
/*使能串口空闲中断*/
__HAL_UART_ENABLE_IT(handle, UART_IT_IDLE);
return MW_SUCCESS;
}
MW_UART_Init接口实现了对串口初始化的功能,HAL库提供的MX_USART1_UART_Init接口仅对硬件进行了配置,在该接口后调用MW_UART_Init对串口进行使能。因为CFIFO初始化的循环缓冲区用于对DMA接收数据的缓存,因此DMA缓冲区的长度要小于数据缓存区的长度。通过对串口空闲中断的使能和DMA的使能,使能串口数据的接收。
2.2 串口数据接收方案
本文中提供了采用串口空闲中断和DMA接收完成中实现数据接收的方案。假设DMA缓冲区的大小是16,若一次接收的数据长度小于DMA缓冲区的长度,则本次传输通过串口空闲中断获取数据;若一次接收数据的长度大于16,假设为20,则前16字节由DMA完成中断从DMA缓冲区中获取,剩下的4字节数据通过串口空闲中断从DMA缓冲区中获取。
基于上述的方案,在DMA缓冲区满后应自动重新装载缓冲区,因此采用了DMA循环模式。在循环模式中,需要注意DMA缓冲满后,再接收的数据存放的位置。举例,串口要接收的一串字符串为‘’1234567890abcdefghij‘’共20字节,当DMA缓冲区接收了16字符后,DMA缓冲区里的数据为‘’1234567890abcdef‘’并产生DMA完成中断,我们可以利用这个中断将获取数据。由于是循环模式,不需要重新配置,在DMA缓冲区满后继续接收余下的4字节数据,当接收完这四字节数据后,DMA缓冲区中的16字节数据为‘’ghij567890abcdef‘’并产生串口空闲中断,我们可以利用这个中断获取后四节的数据(“ghij”)。值得注意的是,如果此时再接收4个字节的数据(比如“ABCD”)后产生空闲中断,那DMA缓冲区内的数据应是“ghijABCD90abcdef”,需要获取的“ABCD”4字节数据距DMA缓冲区的起始位置存在4字节的偏移,获取的时候需要格外注意。因此在属性中定义了DmaOffset这个参数来声明在DMA缓冲中获取数据的偏移位置。具体实现方式如下:
HAL库提供的中断处理中,并没有对空闲中断进行处理,因此需要在中断中提供对空闲中断处理的接口:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
MW_UART_IRQHandler(&huart1);
/* USER CODE END USART1_IRQn 1 */
}
在HAL库提供的USART1_IRQHandler接口中添加MW_UART_IRQHandler接口实现对串口空闲中断的处理(HAL_UART_IRQHandler为HAl库提供的对中断处理的接口)。
void32 MW_UART_IRQHandler(UART_HandleTypeDef *huart)
{
int32_t RecvNum = 0;
int32_t WriteNum = 0;
int32_t DmaIdleNum = 0;
MX_UART_ATTR *pUartAttr = &sUartAttr;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
/*清除空闲中断标识位,重新接受串口空闲中断*/
__HAL_UART_CLEAR_IDLEFLAG(huart);
/*计算在DMA缓冲区需要获取的数据长度*/
DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);
RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;
/*将获取到的数据放到数据接收缓冲区中*/
WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,RecvNum);
if(WriteNum != RecvNum)
{
loge("Uart ReadFifo is not enough\r\n");
}
/*计算获取数据位置的偏移量*/
pUartAttr->DamOffset += RecvNum;
}
}
通过对HAL库中HAL_UART_RxCpltCallback这个弱函数的重写可以实现对DMA完成中断的处理,这个函数虽然声明在stm32f4xx_hal_uart.c中,其实DMA完成中断最后调用的是串口接收完成的回调函数。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
int32_t DmaLen = 0;
int32_t WriteNum = 0;
MX_UART_ATTR *pUartAttr = &sUartAttr;
/*计算需要获取数据的长度*/
DmaLen = pUartAttr->DmaSize - pUartAttr->DamOffset;
/*将获取的数据存放到数据缓冲区中*/
WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,DmaLen);
if(WriteNum != DmaLen)
{
loge("Uart ReadFifo is not enough\r\n");
}
/*复位DMA偏移量*/
pUartAttr->DamOffset = 0;
}
该接口中也需要计算接收数据的长度的原以为:若上次串口空闲中断接收了4个字节的数据,则再接收12个字节的数据就会产生DMA传输完成中断,因此DMA缓冲区的长度16减去串口中断为DmaOffset赋值的4,得到实际接收的数据长度为12。
两个中断里将DMA缓冲区的数据搬移到数据循环缓冲区中,以供应用去获取和处理:
int32_t MW_UART_Receive(uint8_t* buffer,int32_t len)
{
int32_t RecvNum = 0;
MX_UART_ATTR *pUartAttr = &sUartAttr;
/*从数据循环缓冲区中获取数据*/
RecvNum = CFIFO_Read(&pUartAttr->ReadCFifo, buffer, len);
return RecvNum;
}
应用中,调用MW_UART_Receive接口获取数据并对数据进行处理等后续操作。上述的方案避免了DMA缓冲区长度对单次接收数据长度的限制,如果出现了“Uart ReadFifo is not enough”,说明应用中调用MW_UART_Receive接口的频率不够快,或者接收循环缓冲区不够大。总之,数据接收的完整性应取决于应用上逻辑的实现,而不是需要DMA缓冲区申请的足够大,这也是本文串口数据接收方案的原则。
2.3 串口数据发送方案
串口数据发送中,采用了接收中类似的方案,先将数据暂时保存到循环缓冲区中,然后通过DMA将数据发送出去。在应用中调用MW_UART_Transmit将数据发送出去,传统方案中一次发送的数据量必须小于DMA缓冲区的大小,本方案中一次发送的数据量应小于发送循环缓冲区的余量。
int32_t MW_UART_Transmit(uint8_t* buffer,int32_t len)
{
int32_t TransNum = 0;
int32_t TransLen = 0;
MX_UART_ATTR *pUartAttr = &sUartAttr;
/*将要发送的数据先写入循环缓冲区*/
TransNum = CFIFO_Write(&pUartAttr->WriteCFifo, buffer, len);
/*如果发送DMA未在发送中,则使能发送*/
if(pUartAttr->TransFlag == MW_TRANS_IDLE)
{
TransLen = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);
if(TransLen > 0)
{
pUartAttr->TransFlag = MW_TRANS_BUSY;
if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransLen))
{
loge("Uart Trans_DMA failed\r\n");
}
}
}
return TransNum;
}
该接口中,若TransFlag为MW_TRANS_IDLE,则从循环缓冲区中获取最长不超过DMA缓冲区长度的数据,调用HAL库提供的HAL_UART_Transmit_DMA接口将数据发送出去,将TransFlag置为MW_TRANS_BUSY,发送完成之后响应串口发送完成中断,重写HAL库中提供的HAL_UART_TxCpltCallback弱函数接口实现串口发送完成中断的操作。
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
int32_t TransNum = 0;
MX_UART_ATTR *pUartAttr = &sUartAttr;
/*从发送循环缓冲区中获取数据*/
TransNum = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);
if(TransNum > 0)
{
if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransNum))
{
loge("Uart Trans_DMA failed\r\n");
}
}
else
{
pUartAttr->TransFlag = MW_TRANS_IDLE;
}
}
当一组数据发送完成后,继续从缓冲区中获取数据,若获取到数据,继续调用HAL_UART_Transmit_DMA将数据发送出去,若获取不到数据了,则将TransFlag置为MW_TRANS_IDLE,这样再调用MW_UART_Transmit就能使能重新使能发送。本方案中,如果正处于DMA传输过程,则将要发送的数据放到数据循环缓冲区中,在发送完成中断中去获取数据将其发送,避免了等待DMA传输完成的逻辑。
本文中提及的串口数据接收发送方案最初是因为采用的MCU的RAM很小,想尽量缩减各类缓冲区的长度,出于这样的考虑完成了整体的方案设计。
-
STM32 AD多通道循环采样后DMA保存数据
2018-02-08 16:56:28STM32 AD多通道循环采样后DMA保存数据:描述:用ADC连续采集8路模拟信号,并由DMA传输到内存。ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ。在每次转换结束后,由DMA循环将转换的数据传输到内存中。程序...STM32 AD多通道循环采样后DMA保存数据:
描述:用ADC连续采集8路模拟信号,并由DMA传输到内存。ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ。在每次转换结束后,由DMA循环将转换的数据传输到内存中。程序源码如下:
adc.c
#include "adc.h" #define ADC1_DR_Address ((u32)0x4001244C) vu16 ADCConvertedValue[256]; //采样数据保存 /* AD 采样的IO口配置 */ void RSTGPIOConfig(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //RES_DETECT4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //CURRENT4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //RES_DETECT1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //CURRENT1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //RES_DETECT2 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //CURRENT2 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //RES_DETECT3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //CURRENT3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); } void ADC_Configuration(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); /* DMA1 channel1 configuration ----------------------------------------------*/ DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADCConvertedValue; //内存地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//方向(外设到内存) DMA_InitStructure.DMA_BufferSize = 256;//传输内容大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址固定 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址固定 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据单位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据单位 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA模式:循环传输 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级高 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输 DMA_Init(DMA1_Channel1, &DMA_InitStructure);//配置DMA1的4通道 /* Enable DMA1 channel1 */ //DMA_Cmd(DMA1_Channel1, ENABLE); //CYL 这里是模式 /* ADC1 configuration ------------------------------------------------------*/ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //禁止扫描方式 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 8;//要转换的通道数目 ADC_Init(ADC1, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_239Cycles5); //PB0 RES4 ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_239Cycles5); //PB1 CUR4 ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_239Cycles5); //PC0 RES1 RES_DETECT1 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_239Cycles5); //PC1 CUR1 CURRENT1 ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_239Cycles5); //PC2 RES2 RES_DETECT2 ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 6, ADC_SampleTime_239Cycles5); //PC3 CUR2 CURRENT2 ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 7, ADC_SampleTime_239Cycles5); //PC4 RES3 ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 8, ADC_SampleTime_239Cycles5); //PC5 CUR3 /* Enable the temperature sensor and vref internal channel */ ADC_TempSensorVrefintCmd(ENABLE); ADC_DMACmd(ADC1, ENABLE);/* Enable ADC1 DMA */ ADC_Cmd(ADC1, ENABLE);/* Enable ADC1 */ ADC_ResetCalibration(ADC1);/* Enable ADC1 reset calibaration register */ while(ADC_GetResetCalibrationStatus(ADC1));/* Check the end of ADC1 reset calibration register */ ADC_StartCalibration(ADC1);//ADC校准 while(ADC_GetCalibrationStatus(ADC1));//等待校准完成 DMA_Cmd(DMA1_Channel1, ENABLE); /* Enable DMA1 channel1 */ ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换 }
adc.h
#ifndef __ADC_H #define __ADC_H extern vu16 ADCConvertedValue[256]; void RSTGPIOConfig(void); void ADC_Configuration(void); #endif
应用程序可以通过如下函数读取到AD值:
static float ADC_RES_1(void) //PC0 RES1 RES_DETECT1 对应第3个数据 { u8 i; u32 nAdcData = 0; float RES1 = 0; for (i = 0; i < 32; i++) { nAdcData += ADCConvertedValue[i*8+2]; } RES1 = (nAdcData/32.0)*825/1024.0; // 3300对应的是单片机3.3V 相当于3300/4096 return RES1; }
-
电子-ADC多通道DMA非扫描循环.rar
2019-09-05 16:22:08电子-ADC多通道DMA非扫描循环.rar,单片机/嵌入式STM32-F0/F1/F2 -
STM32定时器触发DMA循环完成数据搬运
2019-10-11 11:41:47通过TIM8的事件触发DMA,从内存中的地址搬运数据到外设的寄存器,例子中的中断部分可以关闭,与功能无关,仅为测试时观察方便。 定时器每产生一次事件(本文以UPDATE为例,CC等其他事件也可实现),DMA被启动一次,... -
ZYNQ petalinux 设备树驱动双DMA循环切换传输数据
2020-07-01 09:04:161,单个DMA每次只能发送一定量的数据,但对于数据源来说数据时源源不断产生的,所以在单个DMA单次发送完成至下一次传输期间,这段时间的数据流失了,所以采用两个DMA实现循环发送数据,防止数据丢失。自定义一个IP核... -
stm32串口采用循环队列+DMA方式接收数据调试总结
2019-07-24 13:59:54最近在项目中给串口的接收添加DMA,遇到的问题: 1、“配置好”DMA后,但是DMA不工作 初始化串口1为接收、DMA1的通道3,并使能相应的外设,外设的时钟也全部打开,但是通过调试发现,DMA就是不传输数据。 问题点... -
stm32f103串口接收队列,DMA循环模式+空闲中断
2021-03-22 12:02:27也就是说,在循环模式下,当DMA传输了一个数据,DMA_CNDTRx传输数量寄存器相应减一,当DMA_CNDTRx传输数量寄存器减为0时,DMA_CNDTRx寄存器将恢复为相应的初始值,不用软件干预,那么,这不就是循环队列的方式吗? -
ZYNQ-Linux设备树驱动下的双DMA循环切换传输数据
2020-06-03 15:58:32由于单个DMA每次只能发送一定量的数据,但对于数据源来说数据时源源不断产生的,所以在单个DMA单次发送完成至下一次传输期间,这段时间的数据流失了,所以采用两个DMA实现循环发送数据,防止数据丢失。 自定义一个IP... -
DMA 循环模式(CYCLIC)的疑问
2017-09-21 15:16:321 如果硬件FIFO只有1KB,正常情况下单次映射就映射512B即可,如果用了循环模式,是否可以映射大于1KB的DMA缓存,通知DMA控制器“当收取2K的时候再来告诉我处理”,就是由DMA控制器在底层循环接收? 2 假定DMA缓存4K... -
STM32CubeMX ADC多通道DMA(循环传输Circular,单次Normal)
2020-06-28 00:54:28HAL_ADC_Start_DMA(&hadc1,(uint32_t *)AD_Value,20); 上面配置如果是word,则更改一下定义就好了,cube生成的代码会随着变: uint32_t AD_Value[20]; //main.c文件 HAL_ADC_Start_DMA(&hadc1,(uint32_t *)... -
SDIO相关文章收集,包括:STM32的SDIO的DMA模式死循环
2020-03-06 23:01:42《STM32的SDIO的DMA模式死循环》这个文章解决我一个长时间存在的一个BUG,方法为修改SDIO_TRANSFER_CLK_DIV来修改数据传输速率; 《STM32 SDIO折腾记》这位写个够认真的。 在《STM32F42xx and STM32F43xx Errata ... -
STM32——多路ADC通道+DMA+定时器循环采集传感器数据(二)
2018-07-12 17:36:06承接上一篇,接着讲ADC_DMA功能配置,直接存储器访问(DMA,Direct Memory Access)用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。在配置后,可以在无需任何CPU操作的情况下通过DMA快速移动数据,... -
m32 dma
2014-03-27 18:21:44采用DMA循环模式来采集的,不需要考虑数据覆盖,而是要进行多次采集求平均值,无须考虑数据是否发生了覆盖。 采用DMA正常模式采集的,DMA完成会产生标志,可以用软件查询也可用中断,将数据处理完后,可再次启动DMA... -
STM32——多路ADC通道+DMA+定时器循环采集传感器数据(一)
2018-07-12 16:39:54本文的主要内容是使用STM32F407的ADC1来采集8路通道的传感器数据,因为要定时采集,所以使用了定时器功能,另外需要开启DMA功能。首先说一下程序的思路:使用ADC的DMA功能,将ADC数据寄存器的值通过DMA传到内存的... -
STM32 DMA
2019-07-24 15:12:381、DMA普通模式和循环模式的区别 循环模式:用于处理一个环形的缓冲区,每轮传输结束时数据传输 的配置会自动地更新为初始状态,DMA传输会连续不断地进行。 普通模式:在DMA传输结束时,DMA通道被自动关闭,进一步的... -
freertos dma uart
2016-07-05 10:48:41freertos为系统,stm32f302cb主控芯片,串口利用dma循环接收,效率非常高,不丢自己。循环解析。 -
this.$emit 传输正在循环的数组_基于STM32H7 DMA传输的SPI 应用示例
2021-01-10 01:44:25这里使用stm32h743-Nucleo板做个基于DMA传输的SPI收发应用示例。选择SPI1,MOSI与MISO短接,通过DMA自发自收,分开启Cache和不开启Cache来配置演示,以供参考。利用STM32CubeMx进行配置,生成基于ARM MDK环境和Cube... -
STM32F417xx(Keil)_ADC连续、循环采集两条通道数据(DMA方式).rar
2019-08-01 13:15:12STM32F4_ADC_DMA_连续采集两条通道数据_Keil.zip STM32F4_ADC_DMA_连续采集两条通道数据_Keil.zip