简介:
最近时间比较摸鱼,一直没有更新博客。前几天从好友那里借到了麦克纳姆轮车底盘,感觉这玩意儿挺有趣的,感觉如果单纯用杜邦线将系统板和电机驱动板连接在一起不够好玩,于是做了一个带有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电机驱动开源内容,转载使用请标明出处,如果有问题或者有优化方案,欢迎评论区探讨:)