江義華的部落格(cyh.etlab's blog)
2019年6月12日 星期三
FPGA Verilog 的學習經驗,提供給要入門的新手
1.對自己寫的FPGA Verilog程式,所生成的數位電路要心中有數。 這一點個人認為很重要,就正如寫 C語言,心中要能生成對應的組合語言一樣,我是這樣要求自己的。
雖然 FPGA Verilog語言很像 C語言,但Verilog和C語言在本質上是不同的,因為Verilog進行的是數位硬體設計,寫出來的程式就是實實在在數位電路。所以最重要的就是"對自己寫的FPGA Verilog程式,所生成的數位電路要心中有數"。
2. FPGA Verilog所實現的數位電路,重要的一點觀念是:同步原則,這可由其Verilog語法所生成的RTL Viewer查看(以Quartus為例)即知。然在設計時在異步電路會有不可控性,很可能一不小心可能有毛刺產生,而在整個FPGA內部的任何一點毛刺,都會一級一級的傳遞下去,最終影響系統的穩定性。
正因如此,我在大部分的設計均是採同步電路方式。
而在要採用哪種開發工具,我的觀點是:工具永遠就是工具,最重要的是學會怎麼使用它,不用去太細究研究它,用的多了自然就熟練了。
我在教新人如何入門FPGA Verilog時,時常強調"對自己寫的FPGA Verilog程式,所生成的數位電路要心中有數",會大量使用FPGA Verilog程式與生成的RTL Viewer查看比對。
現我分享, 一個由網路上擷取下來的 moore狀態機的Verilog程式,稍加改寫,並寫一支 Test Bench程式,執行ModelSim產生波形,擷取下來。且也生成 RTL Viewer 查看比對。
有關 FPGA State Machine的寫法與討論,網上很多請自行搜尋查閱。在State Machine我個人常用moore狀態機。
簡單說說moore跟mealy狀態機,無論是mealy還是moore,下一個狀態改變都依賴於input跟目前狀態。兩者不同之處在於其輸出:在mealy,輸出取決於當前輸入和狀態。在moore的輸出,僅是一狀態函數。兩者的優點和缺點:mealy會更快。從某種意義上說,輸入轉換會在輸入轉換符合邏輯時立即改變,這樣mealy是很快反映,但mealy是異步的。
在moore,它必須等待一個時鐘週期,因為moore隨著狀態而變化,moore是同步的。
兩者缺點:moore的輸出比mealy會慢一個 Clock。 而 mealy 最重要的缺點是可能會導致Metastability(動態穩定性),因而可能有 "毛刺" 產生,以導致影響系統的穩定性。
現分享,為讓自己完全清楚: 自己寫的FPGA Verilog程式[如圖1],所生成的數位電路要心中有數[如圖2]。 以及明明白白清楚會產生如何的波形[如圖3]。
2019年2月2日 星期六
使用Arduino完成 RFID 結合Keypad鍵,可控制門鎖(或開關)之應用系統
此範例參考自 Arduino 官網的RFID範例並改寫[註]。官網的周邊與我的有差異,所以需自行修改程式碼以符合我手上的硬體周邊。
此範例硬體系統有:
1. Arduino Uno(也可以是任何CPU)
2. MFRC522 RFID Reader 與 RFID tag
3. 4X4 Keypad
4. led *3, 左邊 led 在偵測到合法 RFID tag 亮起、 中間led 在提示輸入密碼後,正確輸入 3939(正確的密碼)後 亮起、右邊led 是輸入錯誤的密碼亮起; 當偵測到不合法 RFID tag時,亮 中間與右邊的led燈。
合法RFID UID是: "3D AB BD B2"
密碼: 3939
影片網址:
我原本要找一支應用範例,以 RTOS(即時作業系統)方式改寫,後來觀察此應用情境,看起來應該不需用到RTOS,所以就簡單改完後分享。
FB: https://www.facebook.com/groups/233019834173788/permalink/389822991826804/
[註]:
1. Raspberry 樹梅派 與 Arduino 在網上有非常多的 應用範例,參考後稍微改改,可以讓初學者快速了解。
2. 而在Raspberry 樹梅派是嵌入式領域,最好能學到也可以在任何不同的 ARM SoC(例如 Freescale/NXP 的i.MX任意CPU),寫上一支Linux AP透過Linux驅動程式控制硬體。
3. 而在Arduino,可以隨意接上任意周邊,看著周邊硬體的Spec,寫出正確的驅動程式
2018年12月31日 星期一
Qt 5 Signal/Slot連接方法技術細節分享
基於字串的連接語法
|
基於functor-based的連接語法
|
|
Type checking在何時完成
|
Run-time
|
編譯時
|
可以執行隱式類型轉換
|
Y
|
|
可以將信號連接到lambda表達式
|
Y
|
|
可以將信號連接到參數多於信號的插槽
|
Y
|
|
可以將C ++函數連接到QML函數
|
Y
|
2018年8月16日 星期四
深度探索Arduino[In-depth exploration Arduino] 之 2 :在Arduino上執行 FreeRTOS創建兩個Task(TaskBlink1,TaskBlink2),加入外部中斷控制,利用Binary semaphores實現中斷與TASK同步
範例DEMO影片:
功能描述:
TaskBlink1 執行 void TaskBlink1(void *pvParameters) 函數,其優先權是1(數值越低優先權越低,0是最低優先權),TaskBlink1僅在固定時間將LED做toggle(閃爍切換)。
TaskBlink2 執行 void TaskBlink2(void *pvParameters) 函數,其優先權是3 高優先權。而TaskBlink2必須成功“獲取” semaphore時,才往下執行程式,所以 TaskBlink2 嘗試“獲取”semaphore時,當無法獲取時會進入阻塞狀態(Blocked state),且這裡程式設定阻塞時間最大的 'ticks' 數是portMAX_DELAY,所以 TaskBlink2 會被阻塞(Blocked)在此,等待“獲取”semaphore。程式片段如下:
if( xSemaphoreTake( xSemaphore, portMAX_DELAY ) == pdTRUE )
此 FreeRTOS/Arduino程式範例加入外部中斷,設定buttonPin 2有按下時,在下降緣觸發 ExternalInterrupt 中斷Handler,此 ExternalInterrupt 中斷Handler是送出一semaphore,來實現 ExternalInterrupt中斷與 TaskBlink2同步。程式片段如下:
void setup() {
...
pinMode(buttonPin, INPUT);
attachInterrupt(digitalPinToInterrupt( buttonPin ), ExternalInterrupt, FALLING);
...
}
static void ExternalInterrupt()
{
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
if( xHigherPriorityTaskWoken == pdTRUE )
vPortYield();
}
當TaskBlink2成功獲取semaphore時,就往下執行,讓另一個LED亮一下,程式片段如下:
void TaskBlink2(void *pvParameters) // This is a task.
{
(void) pvParameters;
pinMode(12, OUTPUT); // initialize digital LED on pin 12 as an output.
digitalWrite(12, HIGH); // turn the LED on (HIGH is the voltage level)
for (;;)
{
if( xSemaphoreTake( xSemaphore, portMAX_DELAY ) == pdTRUE )
{
digitalWrite(12, LOW); // turn the LED off by making the voltage LOW
vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for one second
digitalWrite(12, HIGH); // turn the LED on (HIGH is the voltage level)
}
}
}
最後一個很重要的觀念,簡單介紹,FreeRTOS的Binary semaphores與Mutual exclusion兩者功能很相似,但有一些細微差別。Mutual exclusion包括優先級繼承機制,而Binary semaphores是不包含。所以使得Binary semaphores成為實現像任務之間或任務與中斷之間的同步是更好的選擇,那Mutual exclusion是實現簡單互斥的最佳選擇。
完整程式碼:
2018年8月8日 星期三
使用VMware Workstation 12 Player開啟 Linux OS虛擬機:以Ubuntu 14.04 LTS為例
1. 免費虛擬機器VMware Workstation 12 Player下載與安裝,請參考:
http://blog.xuite.net/yh96301/blog/63289807 網址步驟 1 到 17.點選「Finish」。
2. 請先下載已經在 VMware Workstation Player上安裝好的Linux OS作業系統壓縮檔,下載後請先解壓縮在夠大的硬碟空間。
[註]: 下載網址會事先EMAIL通知, 建議安裝VMware Workstation 12 Player,當然也可以選擇安裝 VMware Workstation 13 Player以後的版本
3. 以Administrator權限執行VMware Workstation Player。
4. 在VMware Workstation Player開啟的視窗如圖示,點選「Open a Virtual Machine」,可以在虛擬機器執行預先安裝好的Linux OS作業系統。
5. 至step 2所解壓的硬碟目錄,開啟對應的 vmx 檔。
5.1 點選 Edit virtual machine settings,設定適當的執行參數
5.2 於Virtual Machine Setting的 hardware 頁籤,依據你的電腦之CPU核心數與RAM大小設定適當的參數值:
[註]:此處是設定 Memory 8GB, Processor 是4
6. 之後點選 Play virtual machine,執行此 Linux Kernel Image。
VMware Workstation Player成功執行預先安裝好的Linux OS作業系統,畫面:
7. 至此已成功在VMware Workstation Player執行預先安裝好的Linux OS作業系統。
8. 結束VMware Workstation Player上執行的Linux OS作業系統, 記得正確關機,如下圖所示。
之後點選右邊的 [Shut Down] 。
注意事項:
1.有可能執行錯誤狀況: 在VMware Player中啟用UEFI模式
2.確定自己的CPU是否為64位元之外,也要檢查在開機時登入的BIOS裡,Intel VT-x是否有啟動
3.查詢CPU是否有支援Intel VT-x → Intel or AMD
PS: 儘可能安裝 VMware Workstation 12 Player版本(還是VMware Workstation 14 Player??)
4.電腦或筆電的CPU不要太弱(如賽揚Celeron的就不行),硬碟空間須有12G以上,RAM記憶體要8G以上
解決參考網址:
1. https://hk.saowen.com/a/d7a36aaba964cccc4bda293c1f720c51e1b59350364887bd6493fd95be82c339
2. http://ktess1020.pixnet.net/blog/post/286039357-vmware-player-%E5%AE%89%E8%A3%9D64%E4%BD%8D%E5%85%83%E7%B3%BB%E7%B5%B1
3. http://keikoblog.blogspot.tw/2011/04/vmware-64-bit-guest-os-support.html
2018年8月2日 星期四
Python 源碼解析之 list 與 tuple 的 特性與 操作methods 比較
得知了 list 與 tuple 的 methods 之差異,例如 list 可以 sort 排序, 而 tuple 就不能 sort 排序;
以及兩者的特性:
List其特性有:
List is a collection which is ordered and changeable. Allows duplicate members.
且tuple因其特性有:
Tuple is a collection which is ordered and unchangeable. Allows duplicate members.
是ordered順序性與unchangeable(不可改變)的特性。
內文開始:
使用Python語法開發的工程師,應該都知道list 與 tuple這兩種的差異性。也應熟知它們之間可用的methods,list是比較多,tuple是比較少。
List其特性有:
List is a collection which is ordered and changeable. Allows duplicate members.
且tuple因其特性有:
Tuple is a collection which is ordered and unchangeable. Allows duplicate members.
是ordered順序性與unchangeable(不可改變)的特性。為何呢??
當你是資深Python工程師,我個人觀點是要很深入了解,Python其底層源碼是什麼? 才會讓 List 與 tuple 兩種有些差異性。 [ 註: 或者是從事 Python 程式設計講師 也是要非常熟源碼 ]
先剖析 List 是 ordered and changeable;tuple是 ordered and unchangeable。
觀察Python 源碼的 listobject.h 與 tupleobject.h,
在 listobject.h 有 PyListObject 的宣告:
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
在 tupleobject.h有 PyTupleObject 的宣告:
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
} PyTupleObject;
兩者第一個 struct member是 PyObject_VAR_HEAD,這定義在 object.h,有關之程式碼如下:
#define PyObject_VAR_HEAD PyVarObject ob_base;
PyObject_VAR_HEAD是個巨集,其marco defination是 PyVarObject ob_base;
再來 PyVarObject 的定義如下:
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;
PyVarObject第一個member是PyObject,其定義如下:
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
PyObject第一個member是 _PyObject_HEAD_EXTRA ,其定義如下:
再分析一下源碼的 _PyObject_HEAD_EXTRA,
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
#define _PyObject_EXTRA_INIT 0, 0,
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
這段是如果定義了 Py_TRACE_REFS 則 Objects 會變成是一個雙向列表, 但是在release版本下沒有定義 Py_TRACE_REFS , 因此這個 MARCO 可以忽略.
最終的 list 與 tuple 之結構型態的 member 呈現分別是:
在 listobject.h 有 PyListObject 的宣告:
typedef struct {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Py_ssize_t ob_size;
PyObject **ob_item; //此處PyObject宣告了List的 ob_item
Py_ssize_t allocated;
} PyListObject;
在 tupleobject.h有 PyTupleObject 的宣告:
typedef struct {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Py_ssize_t ob_size;
PyObject *ob_item[1]; //此處PyObject宣告了 tuple 的 ob_item
} PyTupleObject;
在最終的 list 與 tuple 之結構型態知道,list 是以 PyObject **ob_item; 來處理其 item data;
而 tuple 是以 PyObject *ob_item[1]; 來處理其 item data。
因其型態特性不同,致在其對應的PyTypeObject也不同。
list 的 PyTypeObject 源碼在 listobject.c,程式如下:
PyTypeObject PyList_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"list",
sizeof(PyListObject),
0,
(destructor)list_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)list_repr, /* tp_repr */
0, /* tp_as_number */
&list_as_sequence, /* tp_as_sequence */
&list_as_mapping, /* tp_as_mapping */
PyObject_HashNotImplemented, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, /* tp_flags */
list___init____doc__, /* tp_doc */
(traverseproc)list_traverse, /* tp_traverse */
(inquiry)_list_clear, /* tp_clear */
list_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
list_iter, /* tp_iter */
0, /* tp_iternext */
list_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)list___init__, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
PyObject_GC_Del, /* tp_free */
};
tuple 的 PyTypeObject 源碼在 tupleobject.c,程式如下:
PyTypeObject PyTuple_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"tuple",
sizeof(PyTupleObject) - sizeof(PyObject *),
sizeof(PyObject *),
(destructor)tupledealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)tuplerepr, /* tp_repr */
0, /* tp_as_number */
&tuple_as_sequence, /* tp_as_sequence */
&tuple_as_mapping, /* tp_as_mapping */
(hashfunc)tuplehash, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TUPLE_SUBCLASS, /* tp_flags */
tuple_new__doc__, /* tp_doc */
(traverseproc)tupletraverse, /* tp_traverse */
0, /* tp_clear */
tuplerichcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
tuple_iter, /* tp_iter */
0, /* tp_iternext */
tuple_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
tuple_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
};
我們現將注意力放在其 methods 操作方法:
list 的 methods 操作方法,源碼如下:
static PyMethodDef list_methods[] = {
{"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, "x.__getitem__(y) <==> x[y]"},
LIST___REVERSED___METHODDEF
LIST___SIZEOF___METHODDEF
LIST_CLEAR_METHODDEF
LIST_COPY_METHODDEF
LIST_APPEND_METHODDEF
LIST_INSERT_METHODDEF
LIST_EXTEND_METHODDEF
LIST_POP_METHODDEF
LIST_REMOVE_METHODDEF
LIST_INDEX_METHODDEF
LIST_COUNT_METHODDEF
LIST_REVERSE_METHODDEF
LIST_SORT_METHODDEF
{NULL, NULL} /* sentinel */
};
而 tuple 的 methods 操作方法,源碼如下:
static PyMethodDef tuple_methods[] = {
TUPLE___GETNEWARGS___METHODDEF
TUPLE_INDEX_METHODDEF
TUPLE_COUNT_METHODDEF
{NULL, NULL} /* sentinel */
};
到此得知了 list 與 tuple 的 methods 之差異,例如 list 可以 sort 排序, 而 tuple 就不能 sort 排序;
以及兩者的特性:
List其特性有:
List is a collection which is ordered and changeable. Allows duplicate members.
且tuple因其特性有:
Tuple is a collection which is ordered and unchangeable. Allows duplicate members.
是ordered順序性與unchangeable(不可改變)的特性。
2018年6月27日 星期三
指標,網路上資訊好亂喔(1-1): 嵌入式 C&C++ 重點語法技術淺談[連載1] at ET-Lab(挖挖挖技術實驗室)
這裡就我明確所知道的,且要完全遵守ANSI C89、C99或C11標準[註1],重要的更要對照C編譯後的組合語言,比對一下驗證,這樣才能撥亂反正。
首先第一個觀念,因編譯時有最佳化,C的變數不一定會佔記憶體空間,如果加上 & 用以取對應operand的位址時,就會佔記憶體空間。這裡貼出C99的一段敘述:
The unary & operator yields the address of its operand. If the operand has type "type",the result has type "pointer to type"。
程式碼解說:
int a=10;
&a --> 會取得"pointer to type"的型態,那a 是int, 就會得到 a變數的位址,這位址是 int* 的型態(稍後解說)。
1. 驗證 因編譯時有最佳化,C的變數不一定會佔記憶體空間:
一Cortex Mx C語言如下,
int fun1( int n1 )
{
n1++;
return n1;
}
int main(int a1)
{
int x1;
x1 = fun1( 10 );
return x1+a1;
}
C語言跟組語對照如下,看圖說故事:
看到
0x0800046A F7FFFFF9 BL.W fun1 (0x08000460), 會跳到0x08000460,
0x08000460 1C40 ADDS r0,r0,#1
0x08000462 4770 BX lr, 返回 0x0800046A 下一指令
0x0800046E 4603 MOV r3,r0
0x08000470 1898 ADDS r0,r3,r2 <--- 這行是 return x1+a1;
所以驗證了,C的變數不一定會佔記憶體空間,上面情況是以 暫存器 完成,加快執行速度。
再用另一種編譯器(網路上online comliler),一C語言如下,
int main(int num)
{
return num;
}
編譯成組語,就有利用堆疊區記憶體空間了(請參考暫存器 r7 ),看圖說故事:
2018年6月26日 星期二
用C語言擴展Python功能:適用使用時機,要驅動嵌入式Linux新硬體
維基上也說:以 C++ (或 C) 撰寫的模組通常用於延伸 Python 解譯器的功能,以及啟用低階作業系統功能的存取。 及 在操控硬體的場合使用C++(C語言),在快速開發時候使用Python。
因Python是由C語言實作完成的,所以在很多嵌入式系統可以用已經寫好的Python API很容易驅動硬體,但在要增加新硬體,且要能由Python驅動,就一定要先有寫好的硬體驅動程式(C語言),再用C語言擴展Python功能完成類似OS上的AP,用以驅動寫好的新硬體驅動程式(C語言)。
這裡我在ARM板上(任意開發板或樹梅派也行),簡單DEMO一Hello 範例,程式如下:
hellomodule.c
#include <Python.h>
static PyObject* hello_name(PyObject *self, PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name))
{
return NULL;
}
printf("Hello %s!\r\n", name);
Py_RETURN_NONE;
}
static PyMethodDef HelloMethods[] =
{
{"hello", hello_name, METH_VARARGS, "Hello Example"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef hello =
{
PyModuleDef_HEAD_INIT,
"hello", /* name of module */
"", /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
HelloMethods
};
PyMODINIT_FUNC PyInit_hello(void)
{
return PyModule_Create( &hello );
}
使用以下指令編譯
gcc -fpic --shared $(python3-config --includes) hellomodule.c -o hello.abi3.so
再來準備 setup.py,程式如下:
from setuptools import setup, Extension
setup(
name='hello',
version='1.0',
description='Python Package with Hello World C Extension',
ext_modules=[
Extension(
'hello',
sources=['hellomodule.c'],
py_limited_api=True)
],
)
接著下安裝指令:
python3 setup.py install
安裝成功後,即可利用Python程式使用剛安裝好 C 的函式庫,
helloworld.py 程式如下:
import hello
def main():
hello.hello('World')
if __name__ == "__main__":
main()
本範例是在Python3.5下完成,接著驗證,下達指令:
python3.5 helloworld.py
成功!!
有關更詳細的Python API的說明,請參考:
Python Documentation contents
https://docs.python.org/3.5/contents.html
有關實作Python 使用 C 的函式庫,更詳細的說明,請參考:
Extending Python with C or C++
https://docs.python.org/3.5/extending/extending.html
執行過程的畫面:
2018年6月12日 星期二
紀錄: 於ARM SoC開發板上使用 WebCam 完成人臉識別,並點亮對應人臉識別的LED燈
這影片是人臉識別DEMO的錄影,程式裡給了歐巴馬、川普的照片,啟動ARM SoC開發板上的程式,當識別到歐巴馬,點亮第一個LED燈。識別到川普,點亮第二個LED燈。
其他的人 都以unKnown未知歸類,就點亮第三個LED燈。 要仔細看視頻上方的LED燈顯示喔
有關 紀錄: 使用 WebCam 在 Python 中以 OpenCV 完成人臉檢測 的網址如下:
http://cyh-etlab.blogspot.com/2018/06/webcam-python-opencv.html
2018年6月11日 星期一
由識別手寫數字圖像應用程式(plot_digits_classification.py)範例了解機器學習(Machine Learning)的支持向量機(Support Vector Machine)演算法
識別手寫數字圖像應用程式是由支持向量機(Support Vector Machines/SVM),對SVM模型進行訓練,由分類、預測完成手寫數字的識別。執行畫面如下:
什麼是支持向量機(Support Vector Machines/SVM)? 簡單的說:SVM用於分類和回歸的相關監督學習方法,現給定一組訓練樣例,每個訓練樣例被標記為屬於兩類別之一,SVM會尋找使用盡可能寬的邊界來分隔類別的界限。倘若如果無法清楚地分隔兩個類別,SVM演算法就會盡量找出最佳界限。如圖示,將綠圓點跟紅圓點用一超平面(hyper-plane)分成兩類:
現透過幾個簡單情景範例說明,要找出正確的超平面(hyper-plane),圖示法式很容易理解的:
情景-1:
有三個超平面(hyper-plane)A,B和C。現在,要找出正確的超平面(hyper-plane)來分類"藍色星型"和"紅色圓"。在這種情景,超平面“B” 完成了這項工作。
情景-2:
有三個超平面(hyper-plane)A,B和C。最近數據點(任一類)和超平面(hyper-plane)之間的距離(邊距)將有助於我們決定正確的超平面(hyper-plane)。
在這種情景,超平面“C” 完成了這項工作。
情景-3:
有些人可能選擇了超平面B,因為它具有比A更高的邊距餘量。這是錯的,所以SVM在最大化邊界之前,要準確地對類別進行分類。在這種情景,超平面“A” 完成了這項工作。
情景-4:
情景-5:
在上面的情景中,兩個類之間不能有線性超平面,那要如何對這兩個類進行分類?這裡將添加一個新的特徵 z = x^2 + y^2。現在,重新繪製x軸和z軸上的數據點,如下:
如上圖SVM中,這兩個類之間很容易有一個線性超平面了,那在原始的x y平面空間中查看超平面時,它看起來像一個圓圈:
上述的Image圖參考自Analytics Vidhya。在Python中,scikit-learn是實現機器學習算法的廣泛使用的庫,SVM也可以在scikit-learn庫中使用,並遵循相同的結構(導入庫,對象創建,擬合模型和預測)。讓我們看看識別手寫數字圖像應用程式(plot_digits_classification.py)範例的代碼:
print(__doc__)
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause
# Standard scientific Python imports
import matplotlib.pyplot as plt
# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics
# 數字資料集(The digits dataset)
digits = datasets.load_digits()
# The data that we are interested in is made of 8x8 images of digits, let's
# have a look at the first 4 images, stored in the `images` attribute of the
# dataset. If we were working from image files, we could load them using
# matplotlib.pyplot.imread. Note that each image must have the same size. For these
# images, we know which digit they represent: it is given in the 'target' of
# the dataset.
images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:4]):
plt.subplot(2, 4, index + 1)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Training: %i' % label)
# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)
data = digits.images.reshape((n_samples, -1))
# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.0001)
# We learn the digits on the first half of the digits
classifier.fit(data[:n_samples // 2], digits.target[:n_samples // 2])
# Now predict the value of the digit on the second half:
expected = digits.target[n_samples // 2:]
predicted = classifier.predict(data[n_samples // 2:])
print("Classification report for classifier %s:\n%s\n"
% (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))
images_and_predictions = list(zip(digits.images[n_samples // 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
plt.subplot(2, 4, index + 5)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Prediction: %i' % prediction)
plt.show()
上述程式碼,有關SVM的部分,也是這支程式重要的地方如下,含程式注釋:
# 創建一個支持向量機(Support Vector Machine)分類器
classifier = svm.SVC(gamma=0.0001)
# 訓練 SVC 模型
classifier.fit(data[:n_samples // 2], digits.target[:n_samples // 2])
expected = digits.target[n_samples // 2:]
# 預測測試樣本的數字值
predicted = classifier.predict(data[n_samples // 2:])
其中 svm.SVC 的 SVC是個class,原始程式碼在 classes.py, class SVC完整宣告如下(刪掉注釋部分):
class SVC(BaseSVC):
def __init__(self, C=1.0, kernel='rbf', degree=3, gamma='auto',
coef0=0.0, shrinking=True, probability=False,
tol=1e-3, cache_size=200, class_weight=None,
verbose=False, max_iter=-1, decision_function_shape=None,
random_state=None):
super(SVC, self).__init__(
impl='c_svc', kernel=kernel, degree=degree, gamma=gamma,
coef0=coef0, tol=tol, C=C, nu=0., shrinking=shrinking,
probability=probability, cache_size=cache_size,
class_weight=class_weight, verbose=verbose, max_iter=max_iter,
decision_function_shape=decision_function_shape,
random_state=random_state)
其__init__建構函數的參數有很多,最重要的是 C=1.0, kernel='rbf', degree=3, gamma='auto',我們可以透過調整SVC的參數,以有效地提高此模型性能,這重要參數,“kernel”,“gamma”和“C” 簡單說明如下:
kernel:kernel可用的選項,如“linear”,“rbf”,“poly”等等(默認值是“rbf”)。這裡“rbf”和“poly”對於非線性超平面很有用。
gamma:'rbf','poly'和'sigmoid'的kernel係數。伽馬值越高,將盡力準確地按照訓練數據集進行擬合。
C: 誤差項的懲罰參數C. 它還控制平滑決策邊界和正確分類訓練點之間的權衡。
到此簡單的透過由識別手寫數字圖像應用程式(plot_digits_classification.py)範例了解機器學習(Machine Learning)的支持向量機(Support Vector Machine)演算法。而SVM演算法也是有相關的優點和缺點,
優點是:
1.它工作得很好,分離的邊界很清晰
2.它在高維空間中很有效。
3.它在維數大於樣本數的情況下有效。
4.它使用決策函數中的一個訓練點子集(稱為支持向量),因此它也具有內存效率。
缺點是:
1.如果我們有大量數據,因為所需的訓練時間較長,它表現不佳
2.當數據集有更多的noise 即目標類重疊時,它也表現不佳
3.支持向量機不直接提供概率估計,這些是使用昂貴的 five-fold cross-validation 來計算的。
2018年6月10日 星期日
紀錄: 使用 WebCam 在 Python 中以 OpenCV 完成人臉檢測
先決條件是必須先安裝好相關的程式: Python、Opencv。。。。。。
就像 Youtube影片一樣,通過WebCam可以很容易地在Opencv的影像中檢測到臉部,完整Python程式碼如下:
import cv2
import sys
from time import sleep
cascPath = "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascPath)
video_capture = cv2.VideoCapture(0)
while True:
if not video_capture.isOpened():
print('Unable to load camera.')
sleep(5)
pass
# Capture frame-by-frame
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Display the resulting frame
cv2.imshow('Video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Display the resulting frame
cv2.imshow('Video', frame)
# When everything is done, release the capture
video_capture.release()
cv2.destroyAllWindows()
現在我們來解析重點程式部分...
OpenCV已經包含許多預先訓練的分類器(classifiers),臉部常用的是 haarcascade_frontalface_default.xml,所以要先加載所需的XML分類器:
cascPath = "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascPath)
接著要將 WebCam 設定為 OpenCV 的視頻源(video source),這樣 OpenCV 可以擷取WebCam的視頻。
video_capture = cv2.VideoCapture(0)
再來循環執行,持續透過 Opencv 的 read()功能從視頻源中讀取一幀(Frame),及返回 ret, frame:
ret是返回值, frame是實際的視頻幀(Frame)讀取(每個循環一幀(Frame))。
ret, frame = video_capture.read()
再來Opencv 讀進來的影像格式預設為BGR,現將其讀取的影像(frame)灰階化得到gray影像。
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
Opencv最重要的人臉檢測函數,使用的是 detectMultiScale函數。它可以檢測出圖片中所有的人臉,並將人臉用vector保存各個人臉的座標、大小(用矩形表示),其函數呼叫如下:
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
detectMultiScale 參數是: gray 是CV_8U類型的矩陣,其中包含檢測對象的圖像。 scaleFactor 是 指定在圖像大小依每個圖像比例縮小的程度。 minNeighbors是指定每個候選矩形必須保留多少個鄰居的參數。此參數將影響檢測到的人臉質量,較高的值會導致檢測數量較少但質量較高。minSize是最小可能的人臉大小。小於此值的人臉將被忽略。
再來是在偵測到的臉部周圍繪製一個矩形。
# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
最後將幀(Frame)顯示。
cv2.imshow('Video', frame)
再來 Python 等待'q'鍵被按下。如果偵測到按下 'q',退出Python。
if cv2.waitKey(1) & 0xFF == ord('q'):
break
最後是離開Python前,釋放capture 的 video。
video_capture.release()
cv2.destroyAllWindows()
2018年4月15日 星期日
於mbed SDK Porting課堂講解Callback(R (*func)() = 0),而隨手Coding完成C/C++程式對應之解說範例
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void addNum( int a,int b){ printf("%d+%d=%d\r\n", a,b,a+b ); }
int addNum2( int a,int b){ return a+b; }
double addNum3( double a,double b){ return a- b; }
template<typename RT,typename argType>
class MATH
{
public:
MATH():personObj(){}
void setFpointer( RT (*funP)(argType,argType) );
union
{
RT (*funP)(argType,argType);
void* vPointF;
}_pOPF;
struct person
{ person(){ printf("person()\r\n"); strcpy(myName,"CYH"); age=9999; }
char myName[8];
int age;
}personObj;
};
template<typename RT,typename argType>
void MATH<RT,argType>::setFpointer( RT (*funP)(argType,argType) )
{
_pOPF.funP = funP;
}
int main(int argc, char** argv) {
MATH<void,int> MATHObj;
MATH<int,int> intMATHObj;
MATH<double,double> doubleMATHObj;
MATHObj.setFpointer( addNum );
MATHObj._pOPF.funP( 100,10 );
intMATHObj.setFpointer( addNum2 );
printf( "intMATHObj._pOPF.funP(200,20)=%d\r\n",intMATHObj._pOPF.funP(200,20) );
doubleMATHObj.setFpointer( addNum3 );
printf( "doubleMATHObj._pOPF.funP(200.2,20.2)=%f\r\n",doubleMATHObj._pOPF.funP(200.2,20.2) );
printf( "%s age is %d\r\n", MATHObj.personObj.myName, MATHObj.personObj.age );
system("PAUSE");
return 0;
}
2018年4月6日 星期五
淺談CUDA 平行運算機制之 cudaMemcpy
一般大多數採CUDA平行運算應用程式,是一開始做一次cudaMalloc,之後不再調用它了。
之後會在x86 Host端跟GPU Kernel端透過cudaMemcpy 在PCIe BUS間互傳資料,所以整體的CUDA平行運算應用程式其瓶頸會在cudaMemcpy函式的呼叫上。 現簡單驗證:
1. 循序執行程式如下,numElements是100000000,程式是將numElements個的float type矩陣相加:
cpu_startTime = clock();
for (int i = 0; i < numElements; ++i)
{
h_C[i] = h_A[i] + h_B[i];
}
cpu_endTime = clock();
執行所花的時間是 Sequential cpu_endTime - cpu_startTime=273.000000。
2. CUDA 平行運算程式如下:
/**
* CUDA Kernel Device code
* Computes the vector addition of A and B into C. The 3 vectors have the same
* number of elements numElements.
*/
__global__ void vectorAdd(const float *A, const float *B, float *C, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements)
{
C[i] = A[i] + B[i];
}
}
GPU 使用 390625 blocks,每個Block 配置 256 threads,測定CUDA 平行運算執行程式執行所花的時間,包含執行GPU CUDA的vectorAdd Kernel程式 與 將結果複製到 x86 Host端,整個所需的時間(PS:如果單測vectorAdd Kernel程式一定是超級快的),程式片段如下:
// Launch the Vector Add CUDA Kernel
cpu_startTime1 = clock();
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, numElements);
err = cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
cpu_endTime1 = clock();
執行所花的時間是 Parallel cpu_endTime1 - cpu_startTime1=167.000000。
cudaMemcpy要複製的memory size是100000000個的float變數,大約380MByte。
若PCI-E 1.0 X16實際資料速率是5 GB/s,那 1ms大約是5M Byte,一對比下來,瓶頸真的是cudaMemcpy了。
執行畫面如下:
2017年12月28日 星期四
江義華的部落格(cyh.etlab's blog)
這裡記錄了我的點點滴滴!我的email: microcyh@seed.net.tw
(嵌入式 Linux 實戰精修班/8051/C++/嵌入式ARM-Cortex M3系統開發進階班 /Linux dervice driver/FPGA)
2017年12月27日 星期三
1. MEGAWIN笙泉公司 MA92G8A系列專用型 ASSP, ARM Cortex M0 SoC微控制器
MEGAWIN笙泉公司在ARM 32位元系列SoC 微控制器(MCU)市場,針對特定應用領域推出專用型解決方案,以創造與眾不同的產品價值。
笙泉,憑藉著在傳統8051 MCU的優異抗干擾能力及極佳的加密性核心技術上,以ARM Cortex-M0為內核,開發出MA92G8A系列專用型ASSP ( application-specific standard product) MCU。
MA92G8A系列MCU,內建多變型防死區脈寬調製模塊( PWM ),快速I/O處理模塊及快速型比較器( ACMP )...等,可快速導入及應用於復雜電機控制系統。另外!專利型DMA傳輸模塊,可支持多種接口顯示屏( TFT-LCD ),可應用於車用/教育/工控/醫療/ ...等中小型顯示面板。
2017年9月4日 星期一
成果紀錄:將uClinux porting 到 STM32F4 Discovery,與執行一支Linux AP 及 執行 lseek 的Linux Device Driver驅動程式
這裡簡單紀錄這一 "將uClinux porting 到 STM32F4 Discovery" 的成果。
參考這些網址: http://www.emcu.it/uCLinux/uCLinux.html 、 https://emcraft.com/products/343, 將 uBoot 、 uClinux、及 ROM file system Image正確燒錄至 STM32F4 Discovery。
硬體連接 透過 USB轉232 連接到STM32F4 Discovery的 PC10/TX, PC11/RX,在HOST端 PC 執行 putty serial console。如下圖:
接著 寫一支 Linux AP C1.c(Hello World), 透過 Cross Compiler 編譯成 C1.exe, 之後正確的傳至 在STM32F4 Discovery執行的 uClinux, C1.exe 放在 /var/tmp 目錄。
再接著 寫 uClinux 的驅動範例與搭配的AP程式、驅動程式 Drv1.c 將其編譯成 Drv.ko、AP程式 AP1.c 編譯成 AP1.exe, 之後正確的傳至 在STM32F4 Discovery執行的 uClinux,也是放在 /var/tmp 目錄。
1. 先執行 ./C1.exe 會看到 Hello CYH,
2. 再來測試於 STM32F4 Discovery 上的 uClinux 執行 Linux Device Driver驅動程式: 先 insmod Drv1.ko, 會看到:
Hello, world
name=cyh , age=9999
之後執行 ./AP1.exe, 正確執行, 畫面如下:
2017 9/5成果紀錄:將uClinux porting 到 STM32F4 Discovery,與執行一支Linux AP 及 執行 lseek 的Linux Device Driver驅動程式
這裡簡單紀錄這一 "將uClinux porting 到 STM32F4 Discovery" 的成果。
參考這些網址: http://www.emcu.it/uCLinux/uCLinux.html 、 https://emcraft.com/products/343, 將 uBoot 、 uClinux、及 ROM file system Image正確燒錄至 STM32F4 Discovery。
硬體連接 透過 USB轉232 連接到STM32F4 Discovery的 PC10/TX, PC11/RX,在HOST端 PC 執行 putty serial console。如下圖:
接著 寫一支 Linux AP C1.c(Hello World), 透過 Cross Compiler 編譯成 C1.exe, 之後正確的傳至 在STM32F4 Discovery執行的 uClinux, C1.exe 放在 /var/tmp 目錄。
再接著 寫 uClinux 的驅動範例與搭配的AP程式、驅動程式 Drv1.c 將其編譯成 Drv.ko、AP程式 AP1.c 編譯成 AP1.exe, 之後正確的傳至 在STM32F4 Discovery執行的 uClinux,也是放在 /var/tmp 目錄。
1. 先執行 ./C1.exe 會看到 Hello CYH,
C1.c 程式如下:
#include <stdio.h>
int main()
{
printf( "Hello CYH\r\n" );
return 0;
}
2. 再來測試於 STM32F4 Discovery 上的 uClinux 執行 Linux Device Driver驅動程式: 先 insmod Drv1.ko, 會看到:
Hello, world
name=cyh , age=9999
之後執行 ./AP1.exe, 正確執行, 畫面如下:
AP1.c 程式如下:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main( int argc, char* argv[] )//stdin=0 out=1 err=2
{
size_t readLen;
int fd;
char BUF[ 64 ]; // mknod /dev/chrDrv1 c 99 0
fd = open ( "/dev/chrDrv1" , O_RDWR );
write ( fd , (void *)"CYH1234567890ABCDEFGHIJ" , 24 );
lseek ( fd , 0, SEEK_SET );
memset(BUF,0,64);
readLen = read ( fd , (void *)BUF, 3 ); // BUF <- "CYH";
printf( "1. BUF=%s\r\n", BUF );
memset(BUF,0,64);
readLen = read ( fd , (void *)BUF, 10 ); // BUF <- "1234567890";
printf( "2. BUF=%s\r\n", BUF );
memset(BUF,0,64);
readLen = read ( fd , (void *)BUF, 10 ); // BUF <- "ABCDEFGHIJ";
printf( "3. BUF=%s\r\n", BUF );
memset(BUF,0,64);
readLen = read ( fd , (void *)BUF, 10 ); // BUF <- EOF or 0;
printf( "BUF=%s\r\n", BUF );
close ( fd ) ;
return 0;
}
2017年7月6日 星期四
添加新的Cortex Mx SoC[ STM32F系列]到mbed-OS IOT物聯網產品之mbed SDK Porting 移植技術課程
業界常常在做有關物聯網新產品開發時,採用ARM mbed OS物聯網開發平台,然目前 mbed online Compiler官網所提供的各廠家有支援 mbed 之SoC CPU,都是特定的型號。舉例: 意法半導體STMicroelectronics在其 STM32F10x 系列,僅提供STM32F103RB,且其周邊零件也僅是公板(型號: NUCLEO-F103RB)有支援的才有釋出對應的 mbed SDK。但產品開發在眾多因素下,均會有指定特定CPU型號與周邊IC零件。上述狀況需要mbed SDK Porting 移植技術,我在規劃一些技術課程時,是以自己在業界的研發經驗為基礎,輔以閱讀大量技術性文件與消化實作後,所產生的一系列課程。【ARM嵌入式物聯網系列:模組B】
IoT物聯網 mbed SDK Porting 移植技術與開發實戰精修班
即將開課,有興趣的人請參考。此課程內容是能直接在Keil裡面建立 mbed 的離線工程,匯入完整 mbed SDK原始碼,讓工程師完全掌握 mbed 底層技術,避開離線編譯 mbed SDK無法看到完整 mbed SDK原始碼的窘境,能夠隨意設置CPU型號,並有能力加入產品所需的周邊IC的SDK API,來加速物聯網產品的開發。
【課程名稱】 | 【ARM嵌入式物聯網系列:模組B】 IoT物聯網 mbed SDK Porting 移植技術與開發實戰精修班 ★全台首開 結合專題實作 實務導向為主 ★贈送ARM-Cortex M3板子(價值5,000) |
【課程代碼】 | 06C004 |
【上課時間】 | 7/16~8/13,每週日,9:00~18:00,共30小時[前二週上到17:00,後二週上到18:00] |
【課程主旨】 | RM 跟他的Partners目前已共同釋出具顛覆性的物聯網統一的開發平臺 mbed。該 mbed 物聯網統一的開發平臺是建立基於ARM的微控制器產品的,它可以讓以後物聯網開發者,無需「重複打造輪子」;物聯網產品開發者使用 ARM 架構的晶片、免費使用 mbed OS,然後最終直接基於 mbed SDK 開發應用層就可以,以此來加速開發和縮短產品的開發時程。但是就像 Embedded Linux、Android 一樣,目前 mbed 官網所提供的各廠家的支援 mbed 之CPU,都是特定的型號。舉例: 意法半導體STMicroelectronics在其 STM32F10x 系列,僅提供STM32F103RB,且其周邊零件也僅是公板(型號: NUCLEO-F103RB)有支援的才有釋出對應的 mbed SDK。但產品開發在眾多因素下,均會有指定特定CPU型號與周邊IC零件。本課程講授mbed SDK Porting 移植技術與開發實戰,讓學員能直接在Keil裡面建立 mbed 的離線工程,能夠隨意設置CPU型號,並有能力加入產品所需的周邊IC的SDK API,加速物聯網產品的開發。 |
【課程特色】 | 1. 採用 ARM Keil工具,匯入完整 mbed SDK原始碼,讓學員完全掌握 mbed 底層技術,避開離線編譯 mbed SDK無法看到完整 mbed SDK原始碼的窘境。 2. 增加多項物聯網進階LAB實戰,以符合業界需求 3. 增加 mbed SDK程式連結網路功能,讓物聯網設備連接上乙太網路 |
【修課條件】 | 1. 熟悉 C & C++ 語言與數位邏輯 2.建議上過【ARM嵌入式物聯網系列:模組A】嵌入式ARM-Cortex Mx系統開發韌體 實作實戰班 |
【課程大綱】 | 1. ARM mbed 開發平臺與 mbed SDK 開發應用層 2. mbed SDK porting 移植技術 3. ARM Cortex Mx CMSIS Module 4. mbed HAL and API 5. mbed的 GPIO 數位輸入輸出與GPIO HAL module driver: ★【LAB】LED顯示與按鍵的使用 6. mbed的 ADC/DAC與ADC/DAC HAL module driver:★【LAB】 ADC和DAC的使用 7. mbed的PWM輸出與PWM OUT API:★【LAB】PWM調光的運用 8. mbed GPIO中斷應用與HAL module driver:★【LAB】mbed中斷 控制運用 9. mbed 時鐘系統與Time HAL module driver:★【LAB】Time時鐘 系統的運用 10. mbed中的RTC與RTC HAL module driver:★【LAB】使用RTC作按 鍵防彈跳的運用 11. mbed UART通訊與UART/USART HAL module driver:★【LAB】 串列通訊綜合應用 12. mbed SPI通訊與SPI HAL module driver:★【LAB】SPI的運用 13. mbed I2C通訊與I2C HAL module driver:★【LAB】I2C的運用 14. mbed 電腦網路應用基礎、mbed TCP/UDP應用程式與mbed HTTP應用 程式設計 ★【LAB】mbed 網路介面實驗(TCP/IP + Web Server+控制板上硬體 ) |
【上課時數】 | 30 小時 |
【上課地點】 | 台北分部(台北市博愛路80號3樓) |
【主辦單位】 | 財團法人自強工業科學基金會 |
【課程費用】 | 15500元 (超值優惠價格需送出報名表後,系統發出報名成功回函確認金額。) |
【超值優惠】 |
|
【諮詢專線】 | 02-23113316分機2287 林小姐 HCLin@tcfst.org.tw |
【學員須知】 | 報名與繳退費方法| 常見問題與解決|會員紅利積點活動辦法 |
【注意事項】 | 若已報名C003已有板子,可享有不含板子價錢12000(不可再折紅利點數),若不要板子 請務必來電告知02-23113316
|
FPGA Verilog 的學習經驗,提供給要入門的新手
今天簡單說說 FPGA Verilog 的學習經驗,提供給要入門的新手: 1.對自己寫的FPGA Verilog程式,所生成的數位電路要心中有數。 這一點個人認為很重要,就正如寫 C語言,心中要能生成對應的組合語言一樣,我是這樣要求自己的。 雖然 FPGA Verilog語言...
-
今天簡單說說 FPGA Verilog 的學習經驗,提供給要入門的新手: 1.對自己寫的FPGA Verilog程式,所生成的數位電路要心中有數。 這一點個人認為很重要,就正如寫 C語言,心中要能生成對應的組合語言一樣,我是這樣要求自己的。 雖然 FPGA Verilog語言...
-
這影片是紀錄 【Python +OpenCV】Python 3 程式設計最佳入門到進階應用實戰工程師培訓班:基礎至進階語法+影像處理與辨識(OpenCV) 課程的DEMO部分,請參考相關網頁: 先決條件是必須先安裝好相關的程式: Python、Opencv。。。。。。 ...
-
使用Arduino完成 RFID 結合Keypad鍵,可控制門鎖(或開關)之應用系統 此範例參考自 Arduino 官網的RFID範例並改寫[註]。官網的周邊與我的有差異,所以需自行修改程式碼以符合我手上的硬體周邊。 此範例硬體系統有: 1. Arduino U...