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控制並不一定要三者都出現,也可以只是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)