STM32F030F4P+PWM复用+DMA+ADC基本配置
STM32F030系列单片机介绍
这里主要介绍的就是单品机是48M的,使用了,IO口控制,PWM复用,DMA,ADC等等。如何需要修改主频可以参考:标准库配置
PWM复用
通过查找手册可以看出来PA6要使用TIM3—CH1来做PWM是要复用的
/*************************************************
Function: PWM_config
Description: tim3定时器通道一 PA6 输出PWM波
Input:
Output:
Return:
*************************************************/
void PWM_config(uint16_t arr , uint16_t psc)
{
GPIO_InitTypeDef GPIO_Motorinit;//配置GPIO结构体初始化
TIM_TimeBaseInitTypeDef TIM_Motorinit;//配置定时器结构体初始化
TIM_OCInitTypeDef TIMPWM_Motorinit;//配置舵机定时器结构体初始化
NVIC_InitTypeDef NVIC_InitStructure;
//1.打开时钟
RCC_APB2PeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);//配置GPIO时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//配置定时器时钟
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_1); //GC6 复用为 TIM3
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;//TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);
GPIO_Motorinit.GPIO_Pin =GPIO_Pin_6;
GPIO_Motorinit.GPIO_Mode = GPIO_Mode_AF;
GPIO_Motorinit.GPIO_OType = GPIO_OType_PP;
GPIO_Motorinit.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Motorinit.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Motorinit);//配置GPIO初始化函数
//3.配置通用定时器结构体
TIM_Motorinit.TIM_ClockDivision = TIM_CKD_DIV1;//设置时钟不分频
TIM_Motorinit.TIM_CounterMode = TIM_CounterMode_Up;//设置计数方式为向上计数
TIM_Motorinit.TIM_Period = arr-1;//设置在下一个更新事件装入活动的自动重装载值为199
TIM_Motorinit.TIM_Prescaler = psc-1;//TIM时钟频率预分频值为7199
TIM_TimeBaseInit(TIM3,&TIM_Motorinit); //配置定时器初始化函数
//4.配置定时器输出PWM结构体
TIMPWM_Motorinit.TIM_OCMode = TIM_OCMode_PWM1;//配置PWM定时器模式为1
TIMPWM_Motorinit.TIM_OutputState = TIM_OutputState_Enable;//使能PWM比较输出
TIMPWM_Motorinit.TIM_OCPolarity = TIM_OCNPolarity_Low;//选择有效输出的极性
TIM_OC1Init(TIM3,&TIMPWM_Motorinit);//配置初始化函数
TIM_Cmd(TIM3,ENABLE);//使能PWM输出
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);//自动加载的预装载寄存器使能
}
1.主要注意的是配置的时候IO口输出模式是复用:
GPIO_Motorinit.GPIO_Mode = GPIO_Mode_AF;
不要不会有PWM波形的
2.主要**注意*的是在配置引脚复用时要选择号对应的复用线:
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_1);
DMA+ADC
在之前使用的过程中对ADC接触不多,使用的多数也是单路ADC。但需要使用两路或多路ADC的时候就需要DMA传输了。本人也不是很懂DMA,也是站在巨人的肩膀上而已。
#define ADC1_DR_Address 0x40012440
uint16_t RegularConvData_Tab[4];
/**
* @file ADC_Configuration
* @brief 完成ADC和DMA的初始化
* @param 无
* @retval 无
*/
void ADC_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 , ENABLE);
RCC_ADCCLKConfig(RCC_HCLK_Div4); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_StructInit(&ADC_InitStructure);//初始化ADC结构
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位精度
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //规定模式装换工作在连续模式
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对其为右对齐
ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward; //ADC的扫描方向
DMA_DeInit(DMA1_Channel1); /* DMA1 Channel1 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设地址
// DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据传输的来源
DMA_InitStructure.DMA_BufferSize = 2;//采集2个通道
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址寄存器不变
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA_Priority设定DMA通道x的软件优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);/* DMA1 Channel1 enable */
ADC_GetCalibrationFactor(ADC1);
ADC_Init(ADC1, &ADC_InitStructure);
/* Convert the ADC1 Channel 0 with 55.5 Cycles as sampling time */
ADC_ChannelConfig(ADC1, ADC_Channel_5 , ADC_SampleTime_55_5Cycles);//ADC_SampleTime_55_5Cycles);
ADC_ChannelConfig(ADC1, ADC_Channel_9 , ADC_SampleTime_55_5Cycles);//ADC_SampleTime_55_5Cycles);
/* ADC Calibration */
ADC_GetCalibrationFactor(ADC1);
ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular); /* Enable ADC_DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADCperipheral[PerIdx] */
ADC_Cmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); /* Wait the ADCEN falg */
ADC_StartOfConversion(ADC1); /* ADC1 regular Software Start Conv */
}
主要解释一下这两行外设地址配置:
1.DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设地址
2. #define ADC1_DR_Address 0x40012440
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;//外设地址
两种配置都能使用。推荐使用第一种。第二种可能需要查手册。
在解释一下我当初想不明白的数据在哪里:
uint16_t RegularConvData_Tab [4];
数据储存在定义的一维数组里面。不是像单路ADC那样调用库转换。后面转换使用在main里面介绍。
main函数介绍
__commom_data Commom_Data;
float ADC_V_Value= 0;
float ADC_I_Value= 0;
/**
* @file main
* @brief Main program.
* @param None
* @retval None
*/
int main(void)
{
static uint32_t timecnt = 0;
static uint32_t toggle = 0;
uint32_t i = 0;
Commom_Data.PWM_Vlaue_led = 50;
LED_GPIO_Config();//初始化LED
TIM2_Config();
ADC_Configuration();
USART1_Config();
PWM_config(100,480);
while (1)
{
TIM_SetCompare1(TIM3,Commom_Data.PWM_Vlaue_led);
ADC_V_Value= (3.3/4096) * RegularConvData_Tab[0]*13;
ADC_I_Value= ((3.3/4096) * RegularConvData_Tab[1])/0.75;
key_Control();
if(Get_ms() - timecnt > 250)
{
timecnt = Get_ms();
toggle = ~toggle;
if(toggle)
GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
else
GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
}
}
}
主要解释的几个数据使用和转换:
1.TIM_SetCompare1(TIM3,Commom_Data.PWM_Vlaue_led);
2.ADC_V_Value= (3.3/4096) * RegularConvData_Tab[0]*13;
3.ADC_I_Value= ((3.3/4096) * RegularConvData_Tab[1])/0.75;
1.就是在循环里不停调用的PWM配置函数。
2.为什么使用3.3V进行转换要看硬件参考的电压是多少,我这边的硬件参考电压是3.3V的。
4096:2的12次方就是4096使用的也是12位的ADC
12位的ADC 最大的数字量是4096, 那么ADC输出值只能在0~4096之间.
注意:电压电流的乘和除的系数要根据外围硬件电路来进行使用。
写在最后 一种很好又简单理解的任务调度使用(基于定时器)
在介绍一种基于定时器的任务调度:
static uint32_t Tim_ms_i=0,Tim_s_i=0 ;
uint16_t Time2;
/**
* @file TIM2_Config
* @brief 调用函数库,初始化定时器2的配置
* @param 无
* @retval 无
*/
void TIM2_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//使能TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
//定时器定时时间T计算公式:T=(TIM_Period+1)*(TIM_Prescaler+1)/TIMxCLK
TIM_TimeBaseStructure.TIM_Period = (10-1);//自动重装载值10--定时时间(10*4800/48M)s
TIM_TimeBaseStructure.TIM_Prescaler =(2400-1);//预分频值,+1为分频系数
TIM_TimeBaseStructure.TIM_ClockDivision =0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能TIM2中断源
TIM_Cmd(TIM2, ENABLE); //使能TIMx外设
}
/*************************************************
Function: uiGet_ms
Description: 获取当前ms数值
Input:
Output:
Return:
*************************************************/
uint32_t Get_ms(void)
{
return Tim_ms_i;
}
/*************************************************
Function: uiGet_s
Description: 获取当前s数值
Input:
Output:
Return:
*************************************************/
uint32_t Get_s(void)
{
return Tim_s_i;
}
/**
* @file TIM2_IRQHandler
* @brief 定时器2中断处理函数
* @param 无
* @retval 无
*/
void TIM2_IRQHandler(void)
{
static uint32_t count_i = 0;
if ( TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET ) //是否发生中断
{
TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update); //清除中断待处理位
Time2--;
Tim_ms_i++;
if(count_i%1000==0)//读取s
Tim_s_i++;
/****end*****/
count_i++;
if(count_i>=60000)//公约数
count_i=0;
}
}
先配置好定时器,在使定时器1ms更新一次。 在通过这种的使用方式进行不同的时间使用不同的函数。
if(Get_ms() - timecnt > 250)
{
timecnt = Get_ms();
toggle = ~toggle;
if(toggle)
GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
else
GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
}
在后续的使用中还是发现了问题,定时时间因为公约数(60000)和u16类型(65535)的限制。在使用超过数据限制的时间是会出现问题。解决发现也是有的在调用前及时的清零。也可以使用u32的类型进行扩大使用范围。