以前通過接觸公司裏的產品,以及自己實際編寫過的代碼,總結出一點分層編程的思維,在這裏分享一下。首先分層思維,就是把整個工程按功能或控製分成幾層(一般小的項目3到4層)以我這次做的藍奧聲智能硬件項目為例來講解一下。
我負責的這個功能塊是一個可以上升、下降的機械部件,這個機械部件叫“X管”。用直流電機驅動“X管”上升、下降,同時帶動一個旋轉編碼器用於獲取實時位置。當然還有一個原點檢測開關。功能要求就是這個機械部件可以手動上下移動,以及通過兩個“一鍵到位”按鈕,讓它移動到指定的兩個位置。
首先介紹一下硬件。MCU是STM32F100,晶振 8.00M。直流電機驅動是一塊藍奧聲科技PWM驅動板,驅動板有3個控製端口,兩個邏輯端口控製電機正反轉,一個PWM輸入控製電機轉速。編碼器就是常見的歐姆龍增量式編碼器,編碼器的計數使用了 TIM3的編碼器計數模式。
這個小項目我把他們分成了3層:
第1層:硬件的配置(電機的控製I/O和編碼器定時器模式及相關I/O的設定)和電器設備的基本控製(比如:電機的正反轉,獲取編碼器的值,清空編碼器值等)可以把這層理解為驅動層。
第2層:具體某個功能的實現,“X管”的上升、下降,“X管”移動到指定的位置,以及“X管”的複位。
第3層:狀態層,怎麼理解這層呢?比如說你按了一下“複位按鈕”,那麼“X管”的複位狀態就置1,然後“X管”就執行“複位函數”自動去尋找原點。直到“X管”複位完成,程序自動清除複位狀態,並且把已複位的狀態置1(這個“已複位”狀態也很關鍵,後面再講)。
***層,硬件的配置和電器設備的基本控製:
第1步:初始化電機的控製引腳、編碼器輸出引腳、PWM輸出配置和定時器編碼
器模式配置(其他的都很常見,隻介紹定時器的編碼模式)
u16 tim3_encoder_val = 0; //TIM3 編碼器模式計數值 encoder_val_ms
/*
普通定時器 TIM3 初始化為編碼器模式
說 明: 用作旋轉編碼器計數(歐姆龍 E6B2_CWZ6C)
參 數: psc 分頻系數, arr 自動重載計數周期
返回值: 無
*/
void TIM3_EncoderInit(u16 psc, u16 arr)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO參數
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //定時器參數
TIM_ICInitTypeDef TIM_ICInitStruct; //編碼器參數
NVIC_InitTypeDef NVIC_InitStruct; //中斷 NVIC 參數
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟 PA 時鍾
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //開啟 TIM3 時鍾
// A相 / B相
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空輸入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//定時器參數設置
TIM_DeInit(TIM3); //把定時器寄存器的值設為默認值
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct); //用默認值填充指定的結構體
TIM_TimeBaseInitStruct.TIM_Prescaler = (psc - 1); //(168-1)設置分頻系數
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //計數方式(TIM9-TIM14隻支持向上計數)
TIM_TimeBaseInitStruct.TIM_Period = (arr - 1); //自動重載計數周期
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //時鍾分頻因子
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
//編碼器模式設置
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Falling); //下降沿
//TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI2, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //上升沿
TIM_ICStructInit(&TIM_ICInitStruct); //用默認值填充指定的結構體
TIM_ICInitStruct.TIM_ICFilter = 6; //指定輸入捕獲過濾器
TIM_ICInit(TIM3, &TIM_ICInitStruct);
//配置中斷 NVIC 優先級分組
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01; //搶占有限級
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x01; //響應優先級
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中斷
NVIC_Init(&NVIC_InitStruct);
TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清除TIM3(中斷)的掛起標誌
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //開啟TIM3的更新中斷
TIM3 -> CNT = 0; //清除定時器計數值
TIM_Cmd(TIM3, ENABLE); //開啟定時器
tim3_encoder_val = TIM_GetCounter(TIM3); //獲取 TIM3 編碼器計數值
//printf("編碼器值:%u\r\n", tim3_encoder_val);
}
第2步:電機驅動和編碼器數值讀取、寫入(編碼器省略)
/*
直流電機驅動
說 明: 注意!!! 在進行大能量正反轉切換時, PWM 占空比緩降為零(≥100ms)後再切換,否則可能造成驅動器損壞
參 數: status 電機狀態。 0停止 1正轉 2反轉
speed 電機速度(0-1000)
返回值: 無
*/
void DC_MotorDrive(u8 status, u16 speed)
{
static u8 stop_sign = 0; //停止標記(電機啟動後必須停止 100ms 以上才可以換方向)
static u8 dir_sign = 0; //電機轉動方向標誌(1正轉 2反轉)
u16 pwm_val = 0; //PWM 輸出占空比
if(status==1 & dir_sign!=2) //正轉
{
dir_sign = 1; //標記電機轉動方向
stop_sign = 1;
DC_MOTOR_FWD = 0; //正轉(有效)
DC_MOTOR_REV = 1; //反轉(無效)
TIM_SetCompare1(TIM1, speed); //設置 PWM 占空比
TIM_Cmd(TIM1, ENABLE); //開啟PWM定時器
}
else if(status==2 & dir_sign!=1) //反轉
{
dir_sign = 2;
stop_sign = 1;
DC_MOTOR_FWD = 1; //正轉(無效)
DC_MOTOR_REV = 0; //反轉(有效)
TIM_SetCompare1(TIM1, speed); //設置 PWM 占空比
TIM_Cmd(TIM1, ENABLE);
}
else if(status==0 | speed == 0)
{
dir_sign = 0;
DC_MOTOR_FWD = 0; //正轉
DC_MOTOR_REV = 0; //反轉(正反轉都位低電平,電機製動)
TIM_SetCompare1(TIM1, speed);
if(stop_sign)
{
//printf("電機開始停止\r\n");
delay_ms(200);
stop_sign = 0;
//printf("電機停止完成\r\n");
}
TIM_Cmd(TIM1, DISABLE); //關閉 TIM1_PWM 定時器
}
}
第二層,功能層:“X管”的上升、下降,以及移動到指定位置
第1步:電機複位,複位雖然並不複雜但卻是電機控製中非常關鍵的一步,隻有通過電機複位,才能確定“X管”***的零點位置,以後“X管”的限位,以及“X管”移動到指定的位置都是相對“X管”的零點位來確定的。
/*
球館狀態
pipe_status 球館狀態值
位0: 球館上升
位1: 球館下降
位2: 球館複位中...
位3: 球館自動移動中...
位4-6: 保留
位7: 球館複位標誌(0未複位 1已複位)
*/
vu8 pipe_status = 0;
u16 pipe_target = 0; //球館目標位置
/*
球館複位
說 明: 球館移動到原點位置
參 數: 無
返回值: 無
*/
void X_Pipe_Reset(void)
{
static u8 status = 0;
u16 site = 0; //球館上下位置
if(!status)
{
if(DI_KEY & 0x10) //不在零點
{
DC_MotorDrive(2, UP_SPEED_FAST); //球館上升
}
else //回到零點
{
DC_MotorDrive(0, 0); //球館電機停止
EncoderReset(); //編碼器複位
status = 1;
}
}
if(status) //已回零點
{
site = GetEncoder_val(); //獲取電機當前位置
if(site < 20) //不在原點
{
DC_MotorDrive(1, DOWN_SPEED_FAST); //球館下降
}
else //到達原點
{
DC_MotorDrive(0, 0); //球館拉杆電機停止
status = 0; //複位完成
pipe_status &= ~0x04; //複位狀態清0
pipe_status |= 0x80; //複位標誌置1
}
}
}
第2步“X管”手動移動,這裏隻用上升來舉例,下降同理
#define UP_LIMIT 20 //球館上限位
#define DOWN_LIMIT 2500 //球館下限位
/*
球館上升
說 明: 隻有複位後,上限位才有效。
參 數: speed 速度
返回值: 無
*/void X_Pipe_up(u16 speed)
{
u16 site;
if(pipe_status & 0x80) //球館已複位
{
site = GetEncoder_val(); //獲取電機當前位置
if(site > UP_LIMIT)
{
DC_MotorDrive(2, speed); //直流電機反轉
}
else
{
DC_MotorDrive(0, 0); //球館電機停止
}
}
else //沒有複位,上限位無效
{
DC_MotorDrive(2, speed); //直流電機反轉
}
}
第3步“X管”移動到指定位置
/*
球館移動到指定位置
說 明: 指定位置是相對於零點位的***位置
參 數: site ***位置,
site 為0或超出限位,終止自動移動狀態
返回值: 無
*/
void X_Pipe_Move(u16 site)
{
static u8 status = 0; //狀態
u16 site2 = 0; //移動中位置
if(!(pipe_status & 0x80)) //球館未複位
{
status = 0;
pipe_status &= ~0x08; //球館移動狀態清0
return; //退出(這裏有多個判斷代碼段,每個判斷段裏執行的程序差不多,可以用goto語句優化一下)
}
if(site>=DOWN_LIMIT || site<=UP_LIMIT) //目標位置超出限位範圍
{
DC_MotorDrive(0, 0); //球館電機停止
status = 0; //狀態清0
pipe_status &= ~0x08; //球館移動狀態清0
return; //退出
}
site2 = GetEncoder_val(); //獲取電機位置
if(site2>=DOWN_LIMIT || site2<=UP_LIMIT) //當前位置超出限位範圍
{
DC_MotorDrive(0, 0); //球館電機停止
status = 0;
pipe_status &= ~0x08;
return;
}
if(!status)
{
if(site > site2)
status = 1; //球館向下移動
else if(site < site2)
status = 2; //球館向上移動
else
{
DC_MotorDrive(0, 0); //球館電機停止
status = 0;
pipe_status &= ~0x08;
return;
}
}
if(status == 1) //球館向下移動(反轉)
{
if((site-site2) > 50) //快速移動
{
X_Pipe_down(DOWN_SPEED_FAST); //球館下降
}
else //慢速移動
{
if(!(site2>=site))
{
X_Pipe_down(DOWN_SPEED_FAST); //球館慢速下降
}
else //到達目標位
{
DC_MotorDrive(0, 0); //球館電機停止
status = 0;
pipe_status &= ~0x08; //球館移動狀態清0
}
}
}
else if(status == 2) //向上移動(正轉)
{
if((site2-site) > 50) //快速移動
{
X_Pipe_up(DOWN_SPEED_FAST); //球館上升
}
else //慢速移動
{
if(!(site>=site2))
{
X_Pipe_up(DOWN_SPEED_SLOW); //球館慢速上升
}
else //到達目標位
{
DC_MotorDrive(0, 0); //球館電機停止
status = 0;
pipe_status &= ~0x08; //球館移動狀態清0
}
}
}
}
第三層,狀態層:根據狀態自動運行(相應的按鈕操作部分很常見,也很簡單所以就省略這部分代碼)
說明:下面的這個“狀態函數”是工程中的一個任務,還有“按鈕掃描”等其他任務,下面舉了兩個列子來說明。
1.假如有人按了一下“複位”按鈕,“按鈕掃描”函數僅僅把複位狀態置1,狀態函數根據狀態值自動執行相應的函數。
2.按了一下“一鍵到位”按鈕,“按鈕掃描”同樣把自動移動狀態置1,並把一鍵到位的位置值賦給 pipe_target 變量。狀態函數根據狀態值自動執行相應的函數。
/*
設備狀態
說 明: 根據設備的狀態執行相應的程序
參 數: 無
返回值: 無
*/
void XH_status(void)
{
if(pipe_status & 0x04) //球館複位狀態置1
{
X_Pipe_Reset(); //球館複位
}
else if(pipe_status & 0x08) //球館自動移動狀態置1
{
X_Pipe_Move(pipe_target); //球館移動到指定位置
}
if(!(pipe_status & 0x7F)) //球館狀態為0
{
DC_MotorDrive(0, 0); //球館電機停止
}
}
好了,以上就是我要分享的分層思維,以及具體的實現過程,歡迎大家指點。
您好,請點擊在線客服進行在線溝通!