开源——基于STM32F030的双路电机驱动板


简介:

  最近时间比较摸鱼,一直没有更新博客。前几天从好友那里借到了麦克纳姆轮车底盘,感觉这玩意儿挺有趣的,感觉如果单纯用杜邦线将系统板和电机驱动板连接在一起不够好玩,于是做了一个带有MCU的双路电机驱动板(不做四路的原因是因为还有一块闲置的电机驱动,而且四路电流太大,我设计也不够严谨,怕电路板会当场变成烟火表演秀)

  闲话不多说,先放一张板子图:

   我们来看看板子上有什么主要元素:

  一块STM32F030K6T6(32脚,较易焊接)

  两个RZ7889电机驱动IC(SOP8)

  MPU6050接口(方便做角度闭环)

  UART接口(通信)

  TIM3接口(与外置电机驱动板连接)

  一个AMS1117(3.3V,板子设计电源为外接6V上下浮动)

  主要系统通过这几个硬件元素实现,主要想法为通过UART接收控制信号,并通过本机实现角度值控制闭环,驱动双电机运行。或者通过上层MCU或ARM处理器输出串口信号,板子实现控制两路电机和附带的双路电机驱动板。如果为编码器电机的话,就需要上层处理器参与处理编码信号(也想到预留编码器接口,但F030的TIM不够,我现有设备也很难焊接引脚更密集的芯片。。)

   此外:板子上也预留了一个IO驱动的LED和一个用户按键。


 

原理图与电路图(附开源)

  电路原理图如下,整个系统比较简单。

  

  电路图有一些缺陷:

  首先,电机供电线GND没有开关电路,在6V电源未供电时,调试时会直接使用3.3V线路驱动,造成我的STLink一直处于峰值电流状态,这显然是不太好的,需要更正。

  走线比较马虎,一些地方未严格符合布线规则。

  布局也较为凌乱(但焊接位置基本都考虑了,不会有阻挡手动贴片焊接的问题)。

  引脚没有在丝印层具体标明,使用不便利,这个需要修正。覆铜的处理也比较粗糙,很多多余的地方。

  所以走线仅供参考,具体使用最好完善。(当然这个板子经过测试,直接使用是没问题的)

  电路图如下:

  其中右方BT6.0为6.0V电源输入。电压可以浮动,但电流需控制在2-3A内,大电流需更改供电线路,RZ7889手册上说明的最大电压为15V,最大电流为5A。(使用大电流电压时注意防护,烟火秀固然好看,但也存在安全风险:))

  实测电流1A长时间工作甚至不会发热,预估最大可以在2.5A电流时稳定工作。

 

  硬件部分开源链接:https://pan.bai我也不知道du.com/s/1xy_F7为什么要加这些文字sP3RBA5OSkOz4r6Kw

  暗号:0hmm


 

软件设计:

  目前完成了初步的软件设计,包括TIM,UART的使用和基本的电机控制。部分代码如下(.h文件请自主补全):

   UART初始化:

void UART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  //复用模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  //配置输出为推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_1);

    
    USART_InitStructure.USART_BaudRate = 115200  ;  //波特率  
    USART_InitStructure.USART_WordLength=USART_WordLength_8b;  //数据帧字长 8位  
    USART_InitStructure.USART_StopBits=USART_StopBits_1;  //配置停止位 1个    
    USART_InitStructure.USART_Parity=USART_Parity_No;  //校验位,无校验位  
    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;  //不使用用硬件流控制    
    USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;  //收发一体                          
    USART_Init(USART1,&USART_InitStructure);  //完成初始化      
    
    USART_Cmd(USART1,ENABLE);  //使能串口
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    
    //优先级配置
    NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;   
    NVIC_InitStructure.NVIC_IRQChannelPriority=0; 
    NVIC_Init(&NVIC_InitStructure);  
    
}

void USART1_SendByte(uint8_t ch)
{
    //先读取TC值状态,使之读取后清零,避免第一个字节丢失
    while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
    USART_SendData(USART1, ch);//向串口1发送数据
    //while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}

void USART1_SendString(char *str)
{
    unsigned int k=0;
    //先执行,后判断  (目前与先判断后执行效果一样)
    do
    {
        USART1_SendByte(*(str+k));
        k++;
    }while(*(str+k)!='\0'); // ‘\0’ 是null字符,在一个字符串的最后,字符数组的大小比字符数多一个
    while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
}

void USART1_IRQHandler (void)
{
    uint16_t temp;
    if(USART_GetITStatus(USART1,USART_IT_RXNE)!= RESET)
    {
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);
        temp = USART_ReceiveData(USART1);  
        USART_SendData(USART1,temp);
    }
}

 

  TIM初始化:

  实际使用时,遇到了一个问题,TIM1CH4的输出极性与前三个相反,编写博客时还未解决,如果哪位知道原因请告知我,感谢。

void TIM1_GPIO_Init(void)
{
    
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  //复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  //推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);  
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);  
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);  
}

void TIM3_GPIO_Init(void)
{
    
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  //复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  //推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_1);
    
}

/*TIM1*/
//freq 频率,这里指TIM输出频率(信号精细度或分辨率)
//dutycycle 占空比,这里设计整数
void TIM1_CH1_PWM(uint32_t freq, uint16_t dutycycle)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    uint16_t tim1_period;
    uint16_t tim1_pulse;
    
    tim1_period = (uint16_t)(24000000/freq-1);  //计算计数周期(决定输出频率),每计满这个数为一个信号周期
    tim1_pulse = (tim1_period+1)*dutycycle/100;
    
    TIM_TimeBaseStructure.TIM_Prescaler = 1;        //设为1,为当前时钟除以2,及24000000Hz
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数模式
    TIM_TimeBaseStructure.TIM_Period = tim1_period;        //定时周期(自动再装载寄存器ARR)
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        //不分频
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        //PWM1模式,与PWM2模式极性不同
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //使能输出
    TIM_OCInitStructure.TIM_Pulse = tim1_pulse;        //脉宽值
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;        //输出极性,这里输出高
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1,ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

void TIM1_CH2_PWM(uint32_t freq, uint16_t dutycycle)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    uint16_t tim1_period;
    uint16_t tim1_pulse;
    
    tim1_period = (uint16_t)(24000000/freq-1);  //计算计数周期(决定输出频率),每计满这个数为一个信号周期
    tim1_pulse = (tim1_period+1)*dutycycle/100;
    
    TIM_TimeBaseStructure.TIM_Prescaler = 1;        //设为1,为当前时钟除以2,及24000000Hz
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数模式
    TIM_TimeBaseStructure.TIM_Period = tim1_period;        //定时周期(自动再装载寄存器ARR)
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        //不分频
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        //PWM1模式,与PWM2模式极性不同
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //使能输出
    TIM_OCInitStructure.TIM_Pulse = tim1_pulse;        //脉宽值
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;        //输出极性,这里输出高
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);
    
    TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1,ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

void TIM1_CH3_PWM(uint32_t freq, uint16_t dutycycle)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    uint16_t tim1_period;
    uint16_t tim1_pulse;
    
    tim1_period = (uint16_t)(24000000/freq-1);  //计算计数周期(决定输出频率),每计满这个数为一个信号周期
    tim1_pulse = (tim1_period+1)*dutycycle/100;
    
    TIM_TimeBaseStructure.TIM_Prescaler = 1;        //设为1,为当前时钟除以2,及24000000Hz
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数模式
    TIM_TimeBaseStructure.TIM_Period = tim1_period;        //定时周期(自动再装载寄存器ARR)
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        //不分频
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        //PWM1模式,与PWM2模式极性不同
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //使能输出
    TIM_OCInitStructure.TIM_Pulse = tim1_pulse;        //脉宽值
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;        //输出极性,这里输出高
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC3Init(TIM1, &TIM_OCInitStructure);
    
    TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1,ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

void TIM1_CH4_PWM(uint32_t freq, uint16_t dutycycle)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    uint16_t tim1_period;
    uint16_t tim1_pulse;
    
    tim1_period = (uint16_t)(24000000/freq-1);  //计算计数周期(决定输出频率),每计满这个数为一个信号周期
    tim1_pulse = (tim1_period+1)*dutycycle/100;
    
    TIM_TimeBaseStructure.TIM_Prescaler = 1;        //设为1,为当前时钟除以2,及24000000Hz
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数模式
    TIM_TimeBaseStructure.TIM_Period = tim1_period;        //定时周期(自动再装载寄存器ARR)
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        //不分频
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        //PWM1模式,与PWM2模式极性不同
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //使能输出
    TIM_OCInitStructure.TIM_Pulse = tim1_pulse;        //脉宽值
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;        //输出极性,这里输出高
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC4Init(TIM1, &TIM_OCInitStructure);
    
    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1,ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}

  

  F0延时:(部分代码行取用于网络)

static uint8_t fac_us=0;
static uint16_t fac_ms=0;

//延时初始化
void delay_init()
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
    fac_us=SystemCoreClock/8000000;
    fac_ms=(uint16_t)fac_us*1000;
}


//延时nus
//nus为要延时的us数.                                               
void delay_us(uint32_t nus)
{        
    uint32_t temp;             
    SysTick->LOAD=nus*fac_us;                     //时间加载               
    SysTick->VAL=0x00;                            //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;    //开始倒数      
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;    //关闭计数器
    SysTick->VAL =0X00;                           //清空计数器     
}


//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 

void delay_ms(uint16_t nms)
{                     
    uint32_t temp;           
    SysTick->LOAD=(uint32_t)nms*fac_ms;                //时间加载(SysTick->LOAD为24bit)
    SysTick->VAL =0x00;                            //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;    //开始倒数  
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;    //关闭计数器
    SysTick->VAL =0X00;                           //清空计数器              
} 

 

  以上就是此次F0电机驱动开源内容,转载使用请标明出处,如果有问题或者有优化方案,欢迎评论区探讨:)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM