簡介:
最近時間比較摸魚,一直沒有更新博客。前幾天從好友那里借到了麥克納姆輪車底盤,感覺這玩意兒挺有趣的,感覺如果單純用杜邦線將系統板和電機驅動板連接在一起不夠好玩,於是做了一個帶有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電機驅動開源內容,轉載使用請標明出處,如果有問題或者有優化方案,歡迎評論區探討:)