2012年10月24日 星期三

程式寫好,非但沒有讚美,反而被罵!

這是以前公司真實事件。


公司機器從我進公司以來,一直有生產調精準度問題。


機器程式到我手上已有四年,產品必須改善功能才能追上市場腳步。


在新加功能中,多了一個檢知器,可以看到工件上標記,並進行加工。


開發過程中,發現系統使用3*3矩陣做映射對位。這塊程式古老到超過十年未有人去動。


為了符合新觀察到的現象,我使用4*4矩陣來做映射對位。


效果出奇的好,精度提高4倍。且生技工程師也稱讚比以前好調很多。


這次修改只動用到軟體,沒有增加任何硬體,等於未加入任何成本。


我認為這是件不錯的事。


 


主管列入績效,卻沒有下文。


去追結果,此項似乎被老闆刪除了。


 


半年後,主管提出我升職申請,再次列入績效內。


很怪的是,我升不了。要不是主管拼了命一再申請,我根本升不了。


 


既然升了,總要報告我的工作績效。


一提到此項功能,我總算了解我升不了的原因。


在研發會議上,老闆指著我這個項目,對著四十幾位工程師面前說:


"這明顯就是一個軟體錯誤,只是修正錯誤,根本不是功能。你根本不夠格談這件事。"


好吧!這件事老闆如此說,我也不回嘴。


接下來,我便報告目前我手上維護機器數目及名稱。大約有五十多機種。


 


三個月後,我離職了。還好接手工程師程度好,沒有問我太多問題。


公司機種大約一百多種,在我手上快一半,銷售數量佔產出機器70%


 


真不知老闆到底在想什麼?



2012年8月12日 星期日

新工作一年感想

一句話:突飛猛進。

工作量大,新技術不斷出現。部門人口暴增三倍。
所以就沒有多少時間寫文章了。

一邊工作,一邊找人。只是,人難找。
找嵌入式系統的人,都是做手機,不想來碰硬體。
找單晶片的人,不懂RTOS。
找馬達控制的人,幾乎沒有工作經驗。
找影像處理的人,不幸的目前無空缺。
有合的真的不多。



簡單多工3.1版


Bee用第3版發展到程式大了,使用的Task數目一多,整個反應時間就慢了。
但有不少Task其實作用時間很短,且工作量很低。
所以就加入了暫時性的工作。
其實考量了可以使用暫時性工作可以用,大部分都是暫時性工作的使用機會高。
這樣就不會一下子增加太多的工作數目,可以保持掃描率,反應不會太慢。

只是暫時性工作要除錯完成再改為暫時性管理方式。因為TaskID一直變的話是不好追蹤的。

新增程式功能說明:

#define MIN_FREE_NUM    3
設定從那一個ID數往後找

unsigned char Get_Temp_Task(void (*p_func)(void));
設定暫時性工作,傳入啟動函式,回傳可用的TaskID。
若回傳為0xFF,則是無空的Task可用。

void Task_Retire(void);
暫時性工作結束

unsigned char Check_Task_Free(unsigned char TaskID);
看工作是否為空閒,主要是用來檢查暫時性工作是否結束。


整體程式:
#include "mt.h"
#define TASK_NUM        4
#define MIN_FREE_NUM    3
extern void SimInit(void);
extern void OS_ENTER_CRITICAL(void);
extern void OS_EXIT_CRITICAL(void);

#define INIT_DEVICE()        SimInit()
#define DIS_INTR()            OS_ENTER_CRITICAL()
#define ENA_INTR()            OS_EXIT_CRITICAL()
// O.S function declaim
void Dummy(void);
unsigned char FuncID;
volatile unsigned char Task_State[TASK_NUM];
unsigned short Task_Delay_Count[TASK_NUM];
void (*TaskFunc[TASK_NUM])(void);

// O.S functions
void InitSystem(void)
{
    extern void Task1(void);
    extern void Task2(void);
    extern void Task3(void);
    unsigned char i;

    FuncID = 0;
    for ( i= 0; i< TASK_NUM ; i++)
    {
        Task_State[i] = TASK_RUNNING;
        Task_Delay_Count[i] = 0;
        TaskFunc[i] = Dummy;
    }
    TaskFunc[0] = Task1;
    TaskFunc[1] = Task2;
    TaskFunc[2] = Task3;
}

void Sleep_Next(void (*p_func)(void))
{
    TaskFunc[FuncID] = p_func;
    Task_State[FuncID] = TASK_WAIT;
}

void WakeUp(unsigned char n)
{
    Task_State[n] = TASK_RUNNING;
}

void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}

void Dummy(void)
{
    Sleep_Next(Dummy);
}

void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_DELAY;
}

void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void))
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = p_func;
    Task_Delay_Count[FuncID] = n;
    Task_State[FuncID] = TASK_SKIP;
}

unsigned char Get_Temp_Task(void (*p_func)(void))
{
    int i = MIN_FREE_NUM;
    while(i < TASK_NUM )
    {
        if( TaskFunc[i] == Dummy )
        {
            TaskFunc[i] = p_func;
            return i;
        }
        i++;
    }
    return 0xff;
}

void Task_Retire(void)
{
    Sleep_Next(Dummy);
}

unsigned char Check_Task_Free(unsigned char TaskID)
{
    return ( TaskFunc[TaskID] == Dummy )? 1: 0;
}

// O.S functions end

// main loop
void main(void)
{
    INIT_DEVICE();
    InitSystem();
    while (1)
    {
        switch (Task_State[FuncID])
        {
        case TASK_RUNNING :
            TaskFunc[FuncID]();
            break;
        case TASK_SKIP :
            if ( Task_Delay_Count[FuncID] )
            {
                Task_Delay_Count[FuncID]--;
                if (! Task_Delay_Count[FuncID]) WakeUp(FuncID);
            }
            break;
        default:
            break;
        }
        if ( ++FuncID >= TASK_NUM ) FuncID = 0;
    }
}

void TimeTick(void)  // This is timer interrupt
{
    unsigned char i;

    for ( i=0; i< TASK_NUM; i++)
    {
        if ( Task_State[i] == TASK_DELAY )
        {
            if ( Task_Delay_Count[i] )
            {
                Task_Delay_Count[i]--;
                if (! Task_Delay_Count[i]) WakeUp(i);
            }
        }
    }
}



開頭檔內容:
void Sleep_Next(void (*p_func)(void));
void WakeUp(unsigned char n);
void Task_Set_Next(void (*p_func)(void));
void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void));
void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void));
unsigned char Get_Temp_Task(void (*p_func)(void));
void Task_Retire(void);
unsigned char Check_Task_Free(unsigned char TaskID);

//  Task State
#define        TASK_RUNNING  0
#define        TASK_SKIP     1
#define        TASK_DELAY    2
#define        TASK_WAIT     3

// O.S. Macro
#define     SET_TASK(id,p_func)     {TaskFunc[id]=p_func;}
#define     SET_STATE(id,state)     {Task_State[id]=state;}
#define     SET_DELAY(id,n)         {Task_Delay_Count[id]=n;}

// Time Constant
#define     SKIP_CYCLE_TIME_US      (0.083)
#define     SKIP_US(x)              ((int)((x)*(1.0/SKIP_CYCLE_TIME_US)))


測試程式:
#include < stdio.h >
#include "mt.h"

unsigned char temp_count;
void TempTask(void)
{
    printf("T");
    temp_count++;
    if(temp_count < 10)
    {
        Task_Set_Skip_Next(SKIP_US(500),TempTask);
    }
    else
    {
        Task_Retire();
    }
}

void Task1(void)
{
    unsigned char task_handle;
  
    printf("G");
    temp_count = 0;
    task_handle = Get_Temp_Task(TempTask);
    WakeUp(task_handle);
    Task_Delay_Ms_Next(10,Task1);
}

void Task2(void)
{
    printf("z");
    Task_Delay_Ms_Next(1,Task2);
}

void Task3(void)
{
    printf(".");
    Task_Set_Skip_Next(SKIP_US(1000),Task3);
}


測試結果:



2012年8月5日 星期日

Cortex-M3 : 遇到HardFault該如何處理

寫Config週邊時最常遇到的是,週邊不動,不然就是HardFault。

若是發展中的程式,還知道現在正在加入的是那一個功能。

但拿到的是舊程式,出現HardFault就糟了,因為從那裏產生的都不知。

其實沒有如此糟,只要去看堆疊內容就行了。

SP+24的位置,就是產生HardFault的位址,一般會是標準函式庫。
就可以查出是那一個呼叫弄錯了。

另外SP+20則是Link Register的內容,可以進一步告知前一個呼叫函式位置。
往前追就可以看到是那一個應用程式弄錯了。

不過大概要會組合語言的人才會知道怎麼回事,原理不多說了。


2012年7月29日 星期日

機器視覺的計算硬體

目前可以使用的計算硬體有:

1. CPU
  這是最傳統的,但計算效能較低。就算是影像用的DSP,效能仍輸以下介紹的硬體。
  但它有一個好處是其他硬體沒有的:良好的除錯及程式修改環境。
 
2. FPGA
  Bee有一段時間是想要做這個,才開始要做就出現了另一個硬體。
  使用FPGA有最快的執行效能,但是要包含記憶體控制才能做得好。
  所以寫程式的難度算是最高的。相對的價格也是高。
  開發慢且修改不易是FPGA影像處理發展上的門檻。
 
3. GPU
  不錯的執行效能及相對便宜的價格。程式開發也還可以,也不需擔心記憶體管理上的瑣事。
  只是起步較晚資料相對少。
 
之所以提這個問題,是看到有人想往機器視覺發展,選擇的路徑有些奇怪。
可能是限制於知識背景,所以使用單一方法去做。

Bee則是因為三種方法都會並無此問題。
問題反而是:正確可以計算的方法為何?
Bee採用的方法是:
先以CPU的架構先去取得資料,採用背景分析的方式去找運算方法。
然後採用GPU的方式去實現,因為程式相近,所以轉換較快速。
再不行的話再將前處理以FPGA去處理,不過還沒走到這一步過。

對於FPGA還不是很熟的人要去做機器視覺,Bee只能說:請您自求多福。    


2012年5月6日 星期日

STM32和RTOS的完美配合

STM32後期,Timer越來越多,和週邊控制相關的信號,Bee皆以Timer做為產生或是觸發源。
另有一些和軟體相關,或是慢速度IO則以RTOS中的時間延時函式來做。
這樣的組合,使STM32無需高度依賴高即時性的RTOS,使用上更加方便。

不過也引入一點問題:系統要如何分割?
在這點上面,Bee目前所處的團隊完全看不出這個問題。因為分工得很好,各思其職。
在名目上有二組人,一組是做系統,一組是做人工智慧。

Bee在系統組內,做IO設定的軟體,也就是利用Timer及RTOS實現各式週邊。
因為工作都是一個人規劃,所以無分割上的衝突。

之所以工作分到Bee這裏來,是因為STM32手冊有1300頁之大。所以有人會說STM32並不好入門。

功能最大的Timer,具有多種能力,可以是Input也可以是Output,連畫電路的工程師都搞不清楚。

最後都是由Bee來建議使用腳位。

基本上要操作STM32,需要Cortex-M3使用手冊,這個不是ST給的。一份stm32 reference manual。
還有一個型號的manual。除了Cortex-M3使用手冊之外,其餘皆為原文。

這種軟硬體間的苦工,就由Bee包了。另外RTOS也是。
RTOS將硬體隔開,人工智慧的程式在測試及移植上較為容易得多。

只需提供好進入函式,不用擔心干擾其他程序,可以盡全力執行。其餘必要工作,不是RTOS負擔,就是Timer和DMA做了。

這樣工程就由數人同時開發。另有一人全力最統合,就是將開發完成的程式組合起來並測試。
系統中還有一位工程師是做介面的,將資料送到PC的介面程式。基本上就是人機界面。

軟體工程師的職務如下
系統工程師1:硬體及驅動,這就是Bee目前的職務
系統工程師2:整合及系統建造
系統工程師3:人機界面
演算法工程師:機器學習及人工智能開發
測試工程師:測試整機成果

若是沒有RTOS,要做到並行開發,那就真的需要很大的規劃了。


2012年4月28日 星期六

簡單多工3版補強

主要是補充巨集。因為結構簡化,使用巨集就可以了。
巨集中的SKIP_CYCLE_TIME_US,請自己填入量測值,現在的4.14是Bee目前用的值,在不同應用值皆不同。
Time Constant是用在 Task_Set_Skip_Next中,可以用    Task_Set_Skip_Next(SKIP_US(25), NextFunction);    使工作延時約25us,但受限於掃描率的時間單位。只是不用自己去算次數,閱讀上也方便些。


void Sleep_Next(void (*p_func)(void));
void WakeUp(unsigned char n);
void Task_Set_Next(void (*p_func)(void));
void Task_Delay_Ms_Next(unsigned int n, void (*p_func)(void));
void Task_Set_Skip_Next(unsigned int n, void (*p_func)(void));


//  Task State
#define  TASK_RUNNING 0
#define  TASK_SKIP  1
#define  TASK_DELAY  2
#define  TASK_WAIT  3


// O.S. Macro
#define     SET_TASK(id,p_func)     {TaskFunc[id]=p_func;}
#define     SET_STATE(id,state)     {Task_State[id]=state;}
#define     SET_DELAY(id,n)         {Task_Delay_Count[id]=n;}


// Time Constant
#define     SKIP_CYCLE_TIME_US      (4.14)
#define     SKIP_TIME_CONSTANT      (1.0/SKIP_CYCLE_TIME_US)
#define     SKIP_US(x)              ((int)((x)*SKIP_TIME_CONSTANT))