滾球經驗總結(PID)

 

1、正點原子STM32F407探索者 + openmv3——串口通信

最近在用stm32f407探索者做滾球,一開始使用的攝像頭是openmv3(openmv4太貴)——實時檢測小球座標(x,y),然後把x和y發送到407開發板的LCD顯示。

openmv的好處是可以使用otsu(大津算法)自動檢測閾值,而ov攝像頭只能手動調閾值才能找出最佳二值化效果。

# 自動檢測閾值
img = sensor.snapshot()
t=img.get_histogram().get_threshold()

 openmv最人性化的就是允許我們在嵌入式上使用Python來編程,比如調用find_blobs()方法,就可以獲得一個列表,包含所有色塊的信息,遍歷所有色塊後找出面積最大的就是小球。

img = sensor.snapshot()  #獲取圖像
    img.binary([th])   # 二值化
    img.draw_rectangle(ROI,color=(220,20,60))   #畫出roi區域
    blobs=img.find_blobs([thresholds], roi=ROI,pixels_threshold=100, area_threshold=100, merge=True)   #根據pixels_threshold=100, area_threshold=100在roi區域尋找所有色塊
    if blobs:
        data=[]                                                                                     
        cx_max = 0                                         #面積最大的色塊x座標清零
        cy_max = 0                                         #面積最大的色塊y座標清零
        pixel_max = 0                                      #面積最大的色塊面積清零
        for b in blobs:
            if b.pixels() >= pixel_max :                   #遍歷所有色塊,找到面積最大的一塊。
                cx_max = b.cx()
                cy_max = b.cy()
                pixel_max = b.pixels()
        img.draw_cross(cx_max,cy_max)
        img.draw_circle(cx_max,cy_max,30)

openmv雖然操作簡單準確,但是我們在openmv和407通信這個地方折騰了很久。

STM32F407有6個串口,每個串口對應的I/O可以從芯片原理圖中對應找到,對於其串口1,PA9端口對應串口1的發送端,PA10端口對應串口1的接收端。下面是我在407開發板的原理圖上截取的串口1的部分: 

 

一開始我們想的很簡單,先初始化串口,然後用write函數發送就ok了。

uart = UART(3, 115200)  #串口3,波特率115200
uart.write(data_out +'\n') 

實際上,openmv得先把數據打包好,設置幀頭,407再根據幀頭按位接收數據。

(一)openmv發送數據

uart = pyb.UART(3, 115200)  #串口3,波特率115200
uart.init(115200, bits=8, parity=None, stop=1)  #8位數據位,無校驗位,1位停止位

def send_data_packet(x, y):
    temp = struct.pack("<bbii",                #格式爲倆個字符倆個整型
                   0xAA,                       #幀頭1
                   0xAE,                       #幀頭2
                   int(x), # up sample by 4    #數據1
                   int(y)) # up sample by 4    #數據2
    uart.write(temp)                           #串口發送

(二)stm32接收數據
      usart.c


extern int ov_frame;
u8 X,Y;    //СÇòµÄ×ø±êÐÅÏ¢

void Data_Processing(u8 *data_buf,u8 num)
{
	int theta_org,rho_org;
    theta_org = (int)(*(data_buf+1)<<0) | (int)(*(data_buf+2)<<8) | (int)(*(data_buf+3)<<16) | (int)(*(data_buf+4)<<24) ;
    X = theta_org;
 
    rho_org = (int)(*(data_buf+5)<<0) | (int)(*(data_buf+6)<<8) | (int)(*(data_buf+7)<<16) | (int)(*(data_buf+8)<<24) ;
    Y = rho_org;
}


void Receive_Prepare(u8 data)
{
    static u8 RxBuffer[10];
    static u8  _data_cnt = 0;
    static u8 state = 0;
    if(state==0 && data==0xAA)
    {
        state=1;
    }
    else if(state==1 && data==0xAE)
    {
        state=2;
        _data_cnt = 0;
    }
    else if(state==2)
    {
        RxBuffer[++_data_cnt]=data;
        if(_data_cnt>=8)
        {
            state = 0;
            Data_Processing(RxBuffer,_data_cnt);
        }
    }
    else
        state = 0;
}

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

代碼中的X,Y就是openmv發送給407的球心座標了。

2、正點原子STM32F407探索者 + OV2640

相比於支持python的openmv,ov系列攝像頭就很原始了,關於ov系列,我們一開始考慮了兩個選擇——2640和7670。

OV7670,30W像素,帶FIFO模塊.......

OV2640,200W像素,不帶FIFO模塊,先讀一行像素,再跳到下一行重新開始讀取新一行像素(見下面代碼第一個if)......

又在淘寶上對比了一系列參數,最終權衡之下選擇了貴了三十塊錢的2640。

2640用到了407芯片自帶的數字攝像頭接口DCMI,DCMI支持DMA。初始化時鐘 →初始化OV2640(初始化IO口 ,上電,並復位 ,執行初始化序列 )→設置圖像窗口設置和圖像輸出大小設置,可以調整圖像大小或者縮放模式 →初始化DCMI(配置相關引腳的複用功能,使能DCMI時鐘,設置DCMI工作模式及PCLK/HSYNC/VSYNC等參數,設置DMA ,啓動DCMI傳輸,即設置DCMI->CR的最低位爲1)(當然這部分代碼我們直接用的正點原子的)。

對照openmv,2640的代碼就通俗易懂多了。

for(i=0;i<B;i++)
    {
      for(j=0;j<=A;j++)
      {
        if(j==A)
        {
          hang++;
          LCD_SetCursor(0,i+1);  
          LCD_WriteRAM_Prepare();		//¿ªÊ¼Ð´ÈëGRAM
         }		
				//LCD->LCD_RAM=rgb_buf[i][j];				
								
         gray=((rgb_buf[i][j]>>11)*19595+((rgb_buf[i][j]>>5)&0x3f)*38469 +(rgb_buf[i][j]&0x1f)*7472)>>16;
         if(gray<=20)                                   //ÕâÀïÊÇСÇòºÚ°×¶þÖµ»¯
         {											  
					 if(i>12&&i<136&&j<150&&j>19)
					 {
						 if(i>X_MAX) X_MAX=i;
						 if(i<X_MIN) X_MIN=i;										 									      
						 if(j>Y_MAX) Y_MAX=j;
						 if(j<Y_MIN) Y_MIN=j;											 
					 }
           LCD->LCD_RAM=WHITE;
         }
         else
         {											 
           LCD->LCD_RAM=BLACK;
         }
				 
       }

3、PID

PID控制器是工業過程控制中廣泛採用的一種控制器,其中,P、I、D分別爲比例(Proportion)、積分(Integral)、微分(Differential)的簡寫;將偏差的比例、積分和微分通過線性組合構成控制量,用該控制量對受控對象進行控制,稱爲PID算法 。

其中KP、KI、KD分別爲比例係數、積分系數、微分系數

P,打個比方,如果現在的輸出是1,目標輸出是100,那麼P的作用是以最快的速度達到100,把P理解爲一個係數即可;而I呢?大家學過高數的,0的積分才能是一個常數,I就是使誤差爲0而起調和作用;D呢?大家都知道微分是求導數,導數代表切線是吧,切線的方向就是最快到至高點的方向。這樣理解,最快獲得最優解,那麼微分就是加快調節過程的作用了。         

P——比例

偏差(目標值減去當前值)與調節裝置的“調節力度”,建立一個一次函數的關係,就可以實現最基本的“比例”控制了。

P越大,調節作用越激進,P越小會讓調節作用更保守。當比較接近目標值時,P的控制作用就比較小了。

但是,只有P不能讓平衡車站起來,水溫也控制得晃晃悠悠,好像整個系統不是特別穩定,總是在“抖動”。

D——微分

D的作用就是讓物理量的速度趨於0,只要什麼時候,這個量具有了速度,D就向相反的方向用力,盡力剎住這個變化。

D越大,向速度相反方向剎車的力道就越強。

如果是平衡小車,加上P和D兩種控制作用,如果參數調節合適,它應該可以站起來了。

I——積分

設置一個積分量,只要偏差存在,就不斷地對偏差進行積分(累加),並反應在調節力度上。

I 的值越大,積分時乘的係數就越大,積分效果越明顯。

所以,I的作用就是,減小靜態情況下的誤差,讓受控物理量儘可能接近目標值。

I在使用時還有個問題:需要設定積分限制,防止在剛開始時,就把積分量積得太大,難以控制。

PID參數調節口訣

參數整定找最佳,從小到大順序查

先是比例後積分,最後再把微分加      //調整順序:P→I→D

曲線振盪很頻繁,比例度盤要放大    

曲線漂浮繞大灣,比例度盤往小扳

曲線偏離回覆慢,積分時間往下降       

曲線波動週期長,積分時間再加長

曲線振盪頻率快,先把微分降下來

動差大來波動慢,微分時間應加長

理想曲線兩個波,前高後低四比一

一看二調多分析,調節質量不會低

若要反應增快,增大P減小I

若要反應減慢,減小P增大I

如果比例太大,會引起系統震盪

如果積分太大,會引起系統遲鈍

PID公式

PID的增量型公式:PIDæ§å¶ç®æ³å¬å¼

離散化公式:è¿éåå¾çæè¿°

最終公式:è¿éåå¾çæè¿°

PID控制並不一定要三者都出現,也可以只是PI、PD控制,關鍵決定於控制的對象。

 

 

 

 

附:openmv滾球完整代碼:gunqiu.py


import sensor, image, time, math
import pyb,utime
import json
from pyb import UART     #加載串口模塊
from pyb import Timer     #加載定時器模塊
from pyb import LED      #加載LED模塊
import struct

ROI=(44,0,230,224)  # 設置檢測區域(興趣範圍)
thresholds = (205, 255)  # 尋找色塊的閾值

#攝像頭初始化
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) 
sensor.set_auto_whitebal(False) 
clock = time.clock()

#openmv串口初始化
uart = UART(3, 115200)  #串口3,波特率115200
uart.init(115200, bits=8, parity=None, stop=1)  #8位數據位,無校驗位,1位停止位

#自動檢測閾值
#img = sensor.snapshot()
#img.draw_rectangle(ROI)
#t=img.get_histogram().get_threshold()

#th  二值化閾值
th=[]
th.append(0)
th.append(80)

#串口數據發送格式
def send_data_packet(x, y):
    temp = struct.pack("<bbii",                #格式爲倆個字符倆個整型
                   0xAA,                       #幀頭1
                   0xAE,                       #幀頭2
                   int(x), # up sample by 4    #數據1
                   int(y)) # up sample by 4    #數據2
    uart.write(temp)                           #串口發送

while(True):
    clock.tick()
    img = sensor.snapshot()
    img.binary([th])
    img.draw_rectangle(ROI,color=(220,20,60))
    blobs=img.find_blobs([thresholds], roi=ROI,pixels_threshold=100, area_threshold=100, merge=True)
    if blobs:
        data=[]                                                    #數據清零
        cx_max = 0                                                  #面積最大的色塊x座標清零
        cy_max = 0                                                  #面積最大的色塊y座標清零
        pixel_max = 0                                               #面積最大的色塊面積清零
        for b in blobs:
            if b.pixels() >= pixel_max :                         #遍歷所有色塊,找到面積最大的一塊。
                cx_max = b.cx()
                cy_max = b.cy()
                pixel_max = b.pixels()
        img.draw_cross(cx_max,cy_max)
        img.draw_circle(cx_max,cy_max,30)

        send_data_packet(cx_max, cy_max)
        print(cx_max, cy_max)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章