1. 程式人生 > 實用技巧 >開源——基於STM32F030的雙路電機驅動板

開源——基於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電機驅動開源內容,轉載使用請標明出處,如果有問題或者有優化方案,歡迎評論區探討:)