文章程式碼顯示

2019年11月5日 星期二

一起學 Python 119 : 使用 iftop 查看網路流量

使用 sudo iftop 即可看到系統對各個 IP 的連線狀況

進去後可以看到本機跟各種 IP 的連線狀況

在這個 tool 裡面並不是使用帶參數的方式來改變其顯示結果

而是使用快捷鍵的方式

如使用

b : 開啟/關閉上方的 bar

B : bar 顯示週期切換 ( 2/10/40 sec )

p : port 顯示

在這邊我們可以開啟 port 顯示以及將週期切換至 10 sec 或 40 sec

觀察得知是哪一個 port 一直在佔用網路頻寬

接著使用

sudo lsof -i :PORTNUMBER

其中 PORTNUMBER 就是你要查詢的 PORT


我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年9月7日 星期六

《筆記》C語言 - 補充_11: 動態配置記憶體空間 calloc 與 struct 與 指標函數 的結合

這邊我就不做過多的解釋,因此部份已經是很深的 C 語言使用
這邊我直接舉一個例子
下例中,我生成十個學生的原始成績(score),並將所有學生的原始成績都加上五(num) 變為 new_score ( new_score = score + 5)

#include "stdio.h"
#include "stdlib.h"

#define numberStudent 10
#define Num 5

typedef struct _Student{
    int No;
    int score;
    int (*calScore_func)(int, int);/* function pointer 注意括弧的位置,不可更動否則含義不同*/
    int new_score;
}Student, *PTR_Student;

/*function prototype 此函數後續用來提供 Student 內的 (*calScore_func) 註冊使用 */
int calScore(int score, int AddNum);

int main()
{
    PTR_Student studentTemp = NULL;
    
    studentTemp = (PTR_Student)calloc(sizeof(Student)*numberStudent, 1);
    
    if (studentTemp == NULL){ /* 確定記憶體空間足夠配置 */
        printf("Allocate the memory fail.");
        return -1;
    }
    else { printf("Allocate the memory success.\r\n\n"); }
    
    int* memoryBeginAddr = studentTemp; /*儲存一下 calloc 回傳的位址,隨意用一個一般的指標變數儲存即可*/
    
    for (int i = 1; i <= numberStudent; i++){
        studentTemp->No = i;
        studentTemp->score = i*10;
        studentTemp->calScore_func = calScore; /* 註冊 calScore_func 指向函數 calScore*/
        /* PS : 函數的名稱與陣列的名稱相同,事實上都被 C 語言單純的視為一個記憶體位址 */
        studentTemp->new_score = studentTemp->calScore_func(studentTemp->score, Num);
        studentTemp++;
    }
    
    studentTemp = memoryBeginAddr;
    
    for (int i = 1; i <= numberStudent; i++){
        printf("student No. %d, score : %d, new_score : %d\r\n\n", 
                studentTemp->No, studentTemp->score, studentTemp->new_score);
        studentTemp++;
    }
    
    //free(studentTemp); /* 注意 free 的對象應該要是當初 malloc 回傳的位址*/
    free(memoryBeginAddr);
    
    return 0;
}

int calScore(int score, int AddNum){
    
    return (score + AddNum);
}




以前我學到這裡的時候覺得這東西太複雜了,一點實際用處也沒有
但真正去工作後才發現這東西有很大的用處
舉個例子,如果我們要對 No.5 的同學成績做不一樣的處理呢?
在最小幅度改動原本程式碼的前提下(此點尤為重要,因為一個產品的改朝換代是不允許大幅度修改程式的)
我們只需要加一個 if 判斷式就可以直接讓 No.5 同學在成績修改的部份改用另一個算式

#include "stdio.h"
#include "stdlib.h"

#define numberStudent 10
#define Num 5

typedef struct _Student{
    int No;
    int score;
    int (*calScore_func)(int, int);/* function pointer 注意括弧的位置,不可更動否則含義不同*/
    int new_score;
}Student, *PTR_Student;

/* function prototype 此函數後續用來提供 Student 內的 (*calScore_func) 註冊使用 */
int calScore(int score, int AddNum);
/* 新增一個函數,其與 calScore 有著一模一樣的輸入變數型態(皆為兩個 int),但在計算的時候我們動點手腳*/
int calScore_special(int score, int muti_Num);

int main()
{
    PTR_Student studentTemp = NULL;
    
    studentTemp = (PTR_Student)calloc(sizeof(Student)*numberStudent, 1);
    
    if (studentTemp == NULL){ /* 確定記憶體空間足夠配置 */
        printf("Allocate the memory fail.");
        return -1;
    }
    else { printf("Allocate the memory success.\r\n\n"); }
    
    int* memoryBeginAddr = studentTemp; /*儲存一下 calloc 回傳的位址,隨意用一個一般的指標變數儲存即可*/
    
    for (int i = 1; i <= numberStudent; i++){
        studentTemp->No = i;
        studentTemp->score = i*10;
        studentTemp->calScore_func = calScore; /* 註冊 calScore_func 指向函數 calScore*/
        /* PS : 函數的名稱與陣列的名稱相同,事實上都被 C 語言單純的視為一個記憶體位址 */
        if ( i == 5 ) { studentTemp->calScore_func = calScore_special; } /*指向不一樣的函數*/
        studentTemp->new_score = studentTemp->calScore_func(studentTemp->score, Num);/*此行完全不變*/
        studentTemp++;
    }
    
    studentTemp = memoryBeginAddr;
    
    for (int i = 1; i <= numberStudent; i++){
        printf("student No. %d, score : %d, new_score : %d\r\n\n", 
                studentTemp->No, studentTemp->score, studentTemp->new_score);
        studentTemp++;
    }
    
    //free(studentTemp); /* 注意 free 的對象應該要是當初 malloc 回傳的位址*/
    free(memoryBeginAddr);
    
    return 0;
}

int calScore(int score, int AddNum){
    
    return (score + AddNum);
}

int calScore_special(int score, int muti_Num){
    
    return (score * muti_Num);
}





我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《筆記》C語言 - 補充_11: 動態配置記憶體空間 calloc 與 malloc

這邊我以 calloc 舉例,並請注意到我 free 的對象

#include "stdio.h"
#include "stdlib.h"

#define numberStudent 10

typedef struct _Student{
    int No;
    int score;
}Student, *PTR_Student;

int main()
{
    PTR_Student studentTemp = NULL;

    studentTemp = (PTR_Student)calloc(sizeof(Student)*numberStudent, 1);
    
    if (studentTemp == NULL){ /* 檢查是否成功配置記憶體空間 */
        printf("Allocate the memory fail.");
        return -1;
    }
    else { printf("Allocate the memory success.\r\n"); }
    
    int* memoryBeginAddr = studentTemp;/* 儲存calloc回傳的起始位址,隨意用一個一般的指標變數儲存即可 */
    
    for (int i = 1; i <= numberStudent; i++){
        studentTemp->No = i;
        studentTemp->score = i*10;
        studentTemp++;
    }
    
    studentTemp = memoryBeginAddr;
    
    for (int i = 1; i <= numberStudent; i++){
        printf("student No. %d, score : %d\r\n", studentTemp->No, studentTemp->score);
        studentTemp++;
    }
    
    //free(studentTemp); /* 注意 free 的對象應該要是當初 malloc 回傳的位址*/
    free(memoryBeginAddr);
    
    return 0;
}





我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《筆記》C語言 - 補充_10: 再戰指標變數、struct 結構 與 void* 的結合


#include "stdio.h"

int main()
{
    typedef struct{
        int pool_1;
        int pool_2;
    }money_pool_small,*PTR_money_pool_small;
    
    typedef struct{
        int pool_3;
        int pool_4;
        int pool_5;
        int pool_6;
    }money_pool_big,*PTR_money_pool_big;
    
    money_pool_small _money_pool_small = {0x1234, 0x5678};
    money_pool_big _money_pool_big = {0x7564, 0x8546, 0x5217, 0x9812};
    
    
    /* 取一個 struct 最基本的寫法 */
    int value;
    value = _money_pool_small.pool_1;
 //get 0x1234 (address = 0xfab6ed78)  
    printf("value = 0x%x (address = 0x%x) \r\n", value, &(_money_pool_small.pool_1)); 
    value = _money_pool_small.pool_2;
 //get 0x5678 (address = 0xfab6ed7c)  
    printf("value = 0x%x (address = 0x%x) \r\n", value, &(_money_pool_small.pool_2)); 

    /* 使用對應的 struct指標 取 struct 數值 */
    PTR_money_pool_small _PTR_money_pool_small = NULL;
    _PTR_money_pool_small = (&_money_pool_small);
    value = _PTR_money_pool_small->pool_1; 
    printf("value = 0x%x \r\n", value); //get 0x1234
    value = _PTR_money_pool_small->pool_2;
    printf("value = 0x%x \r\n", value); //get 0x5678
    
    /* 使用一般的指標變數 取 struct 數值*/
    int* ptr_pool_int = NULL;
    ptr_pool_int = (&_money_pool_small); //取到的是 _money_pool_small 啟始位址
    value = (*ptr_pool_int);
    printf("value = 0x%x \r\n", value); //get 0x1234
    
    ptr_pool_int++; // 指標遞增
    value = (*ptr_pool_int); 
    printf("value = 0x%x \r\n\n", value); //get 0x5678
    
    /* 使用位元數比 struct 內數值小的一般指標變數 取 struct 數值*/
    char* ptr_pool_char = NULL;
    ptr_pool_char = (&_money_pool_small); //取到的是 _money_pool_small 啟始位址
    value = (*ptr_pool_char);
 //get 0x34 (address = 0xfab6ed78)
    printf("value = 0x%x (address = 0x%x) \r\n", value, ptr_pool_char); 
    
    ptr_pool_char++; // 指標遞增
    value = (*ptr_pool_char); 
 //get 0x12 (address = 0xfab6ed79)
    printf("value = 0x%x (address = 0x%x) \r\n\n", value, ptr_pool_char); 
    /********************************************************************
    * 由此我們可以得知, _money_pool_small 的 pool_1 兩個 bytes 的資料(0x1234)在
    * 記憶體中是將低位元(0x34)放置於低地址,此稱為 Little-Endian (小端格式)
    * 而與此對應的有 big-endian (大端格式) 其反將高位元(0x12)放置於低地址
    * 也就是說 big-endian 將一個 int 變數其四個 byte 在記憶體中是 "倒著放的" 
    * (但兩個不同的變數在記憶體中仍是 "順著放的")
    * PS : x86 系列的 CPU 都是 little-endian
    *********************************************************************/
    
    /* 將 int* 轉為 char* 取 struct 數值*/
    int* ptr_pool_int_1 = NULL;
    ptr_pool_int_1 = (&_money_pool_small); //先指向位址,後續要取值時再強制轉型
    value = ( *( (char*) ptr_pool_int_1) ); //注意指標強制轉型的時機與對象以及括弧位置的重要
    printf("value = 0x%x \r\n", value); //0x34
    
    ptr_pool_int_1++;
    value = ( *( (char*)ptr_pool_int_1) ); 
    printf("value = 0x%x \r\n", value); //0x78
    
    /* 使用空指標並強制轉型為一般指標變數 取 struct 數值*/
    void* ptr_pool_void = NULL;
    ptr_pool_void = (&_money_pool_small); //先指向位址,後續要取值時再強制轉型
    value = ( *( (int*)ptr_pool_void ) ); //注意指標強制轉型的時機與對象以及括弧位置的重要
    printf("value = 0x%x \r\n", value); //0x1234
    
    /* 以 PTR_money_pool_small 對 _money_pool_big 取值 */
    PTR_money_pool_small ptr_money_pool_small = NULL;
    ptr_money_pool_small = (&_money_pool_big);
    value = ptr_money_pool_small->pool_1; 
    printf("value = 0x%x \r\n", value); //0x7564
    
    // 指標遞增(假設 int 變數佔據 4bytes, 則此時地址一次是跳8個byte)
    ptr_money_pool_small++; 
    value = ptr_money_pool_small->pool_1;//此時 pool_1 對應到的是 money_pool_big 的 pool_3
    printf("value = 0x%x \r\n", value); //0x5217
    
    /* 使用空指標並強制轉型為 PTR_money_pool_small 取 _money_pool_big (struct) 數值*/
    ptr_pool_void = NULL;
    ptr_pool_void = (&_money_pool_big);
    
    value = ( *( (PTR_money_pool_small)ptr_pool_void ) ).pool_1;
    printf("value = 0x%x \r\n", value); //0x7564
    value = ( (PTR_money_pool_small)ptr_pool_void )->pool_1; //與上上行的寫法等價
    printf("value = 0x%x \r\n", value); //0x7564
    
    
    return 0;
}





我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年9月1日 星期日

《筆記》C語言 - 補充_9: calloc 與普通的變數在內部儲存的方式 (堆(heap) & 棧 (stack) )

1、棧區(stack): 放置為由高地址到低地址
由編譯器(系統)自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。

2、堆區(heap):  放置為由低地址到高地址 *往上堆*
一般由程序設計師分配釋放, 若程序設計師不釋放,程序結束時可能由 OS 回收 。
注意它與數據結構中的堆是兩回事,分配方式類似於鍊結。

 3、全局區(靜態區)(static)
全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 程序結束後有系統釋放


#include "stdio.h"
#include "stdlib.h"

int numer_0_1;
int numer_0_2;
int numer_1_1 = 123;
int numer_1_2 = 456;
static int numer_2_1;
static int numer_2_2;
static int numer_3_1 = 213;
static int numer_3_2 = 555;

typedef struct _Student{
    int No;
    int phone_number;
}Student, *PTR_Student;

int main()
{
    PTR_Student Amy = NULL;
    Amy = calloc(sizeof(Student) ,1);
    
    printf("[calloc]\r\n");
    Amy->No=1;
    Amy->phone_number=123;
    
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);
    printf("The Address of Amy = 0x%x\r\n", Amy);
    printf("The Address of Amy->No = 0x%x\r\n", &(Amy->No) );
    printf("The Address of Amy->phone_number = 0x%x \r\n\n", &(Amy->phone_number) );
    
    printf("[variables]\r\n");
    printf("Global variables\r\n");
    printf("The address of numer_0_1 0x%x \r\n", &numer_0_1);
    printf("The address of numer_0_2 0x%x \r\n", &numer_0_2);
    printf("The address of numer_1_1 0x%x \r\n", &numer_1_1);
    printf("The address of numer_1_2 0x%x \r\n", &numer_1_2);
    printf("Global static variables\r\n");
    printf("The address of numer_2_1 0x%x \r\n", &numer_2_1);
    printf("The address of numer_2_2 0x%x \r\n", &numer_2_2);
    printf("The address of numer_3_1 0x%x \r\n", &numer_3_1);
    printf("The address of numer_3_2 0x%x \r\n", &numer_3_2);
    
    return 0;
}



#include "stdio.h"
#include "stdlib.h"

typedef struct _Student{
    int No;
    int phone_number;
}Student, *PTR_Student;

int main()
{
    PTR_Student Amy = NULL;
    Amy = calloc(sizeof(Student) ,1);
    
    printf("[calloc]\r\n");
    Amy->No=1;
    Amy->phone_number=123;
    
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);
    printf("The Address of Amy = 0x%x\r\n", Amy);
    printf("The Address of Amy->No = 0x%x\r\n", &(Amy->No) );
    printf("The Address of Amy->phone_number = 0x%x \r\n\n", &(Amy->phone_number) );
    
    int numer_0_1;
    int numer_0_2;
    int numer_1_1 = 123;
    int numer_1_2 = 456;
    static int numer_2_1;
    static int numer_2_2;
    static int numer_3_1 = 213;
    static int numer_3_2 = 555;
    
    printf("[variables]\r\n");
    printf("Local variables\r\n");
    printf("The address of numer_0_1 0x%x \r\n", &numer_0_1);
    printf("The address of numer_0_2 0x%x \r\n", &numer_0_2);
    printf("The address of numer_1_1 0x%x \r\n", &numer_1_1);
    printf("The address of numer_1_2 0x%x \r\n", &numer_1_2);
    printf("Local static variables\r\n");
    printf("The address of numer_2_1 0x%x \r\n", &numer_2_1);
    printf("The address of numer_2_2 0x%x \r\n", &numer_2_2);
    printf("The address of numer_3_1 0x%x \r\n", &numer_3_1);
    printf("The address of numer_3_2 0x%x \r\n", &numer_3_2);
    
    return 0;
}




參考連結
堆和棧的區別
堆栈,堆栈,堆和栈的区别
程序的运行时 数据结构



我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《筆記》C語言 - 補充_8: 配置記憶體空間 calloc 與 malloc


#include "stdio.h"
#include "stdlib.h"

typedef struct _Student{
    int No;
    int phone_number;
}Student, *PTR_Student;

int main()
{
    PTR_Student Amy = NULL;
    Amy = (PTR_Student)calloc(sizeof(Student),1);
    
    printf("[calloc]\r\n");
    printf("Before assign the init. value\r\n");
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);
    
    Amy->No=1;
    Amy->phone_number=123;
    printf("After assign the init. value\r\n");
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);

    free(Amy);

    return 0;
}



#include "stdio.h"
#include "stdlib.h"

typedef struct _Student{
    int No;
    int phone_number;
}Student, *PTR_Student;

int main()
{
    PTR_Student Amy = NULL;
    Amy = (PTR_Student)malloc(sizeof(Student));
    
    printf("[malloc]\r\n");
    printf("Before assign the init. value\r\n");
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);
    
    Amy->No=1;
    Amy->phone_number=123;
    printf("After assign the init. value\r\n");
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);

    free(Amy);

    return 0;
}


補充 : 
原本預期 malloc 在 Before assign the initial value 前會取到 "不可預期的數值"
但可能是因為編譯器自動修正的關係所以取到的值亦為 0

但基於編譯器的不同可能導致不同的結果,我們還是必須知道 malloc 在配置記憶體後
其內部的值是不會自動設為 0 的


參考連結
malloc()和calloc()有啥区别


我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《筆記》C語言 - 補充_7: struct 指標 搭配 calloc 配置記憶體空間


#include "stdio.h"
#include "stdlib.h"

typedef struct _Student{
    int No;
    int phone_number;
}Student, *PTR_Student;

int main()
{
    PTR_Student Amy = NULL;
    // calloc 會依 Student 這個 struct 的大小配置記憶體空間
    // 並且回傳該記憶體空間的起始位址(所以我們將這個位址強制轉型為 PTR_Student 並將這個位址儲存
    // 另外, calloc 在配置記憶體空間時會順便把這個空間內全部填滿 0 (而 malloc 不會填 0 )
    Amy = (PTR_Student)calloc(sizeof(Student),1); 
    
    Amy->No=1;
    Amy->phone_number=123;
    
    PTR_Student Tom = NULL;
    Tom = (PTR_Student)calloc(sizeof(Student),1);
    
    Tom->No=2;
    Tom->phone_number=789;
    
    printf("%d, %d \r\n", Amy->No, Amy->phone_number);
    printf("%d, %d \r\n", Tom->No, Tom->phone_number);

    free(Amy);
    free(Tom);
    
    return 0;
}




參考連結
malloc()、free()、calloc() 與 realloc()
malloc()和calloc()有啥区别


我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《筆記》C語言 - 補充_6: enum 的實際用處

之前在學 enum 的時候一直覺得這東西的用處不大
直到進入業界之後,才知道這東西使用的頻率實在太高了
於是我就寫了一個例子來做為一個示範,模擬一下 enum 的實際用處是什麼

在一個小型的專案裡面(例如只有你一個人) ,你大可以不使用 enum
但只要程式變的很大,沒使用 enum 甚至是 struct 的狀況下,程式碼的可讀性就會很差

這邊我用一個 "判斷風扇狀態" 的實際案例做為演示
此例子演示了使用 enum 後,雖然在剛開始做宣告的地方必須多下一點功夫
但這可以讓你後續的程式邏輯都可以用 "文字" 的方式來做呈現

說實話,在實際工作場合中真的很少看到 "判斷式" 裡面會直接輸入一個數字的
因為這樣程式碼的彈性會變的很差,可讀性也會變的很差(也就是說其他同事看到你這串程式碼的時候,會疑惑這個數字是在做什麼的)



#include "stdio.h"

/* 定義 boolean */
typedef enum {
    False,
    True,
}bool;

/* 定義風扇現在的狀態(是否在運轉) */
typedef enum {
    NonWorking,
    Working,
}Fan_Status_bool;
Fan_Status_bool FanStatus = NonWorking; /* 初使化風扇現在的狀態 */

/* 定義風扇現在的轉速等級 */
typedef enum {
    NoSpeed,        // 沒在轉
    LowSpeed = 1000, // 1000 轉
    MidSpeed = 2000, // 2000 轉
    HighSpeed = 3000, // 3000 轉
}Fan_Speed_Level_Status;
Fan_Speed_Level_Status FanSpeedLevel = NoSpeed; /* 初使化風扇的轉速等級 */

/* 藉由此函數, 模擬我們得到一個 fanSpeed */
int getFanSpeed(int test_number);

/* 實務上, 我們通常會建立這樣子的函式用來快速判斷一個風扇的狀態*/
bool isFanWorking(Fan_Status_bool _FanStatus);
bool FanWorking = False; /*初始化 Fan 是否在 working 的狀態*/

/* 用一個函數專門用來 print 出風扇的轉速等級 */
void printFanSpeedLevel(Fan_Speed_Level_Status _FanSpeedLevel);

/* ===== main ===== */
int main()
{
    for (int test_number=0; test_number < 4; test_number++){
        printf("test_number = %d , getFanSpeed frunction return %d ===>  ", 
        test_number, getFanSpeed(test_number));
        
        if ( getFanSpeed(test_number) == NoSpeed){
            FanStatus = NonWorking;
            FanSpeedLevel = NoSpeed;
        }
        else if ( getFanSpeed(test_number) == LowSpeed){
            FanStatus = Working;
            FanSpeedLevel = LowSpeed;
        }
        else if ( getFanSpeed(test_number) == MidSpeed){
            FanStatus = Working;
            FanSpeedLevel = MidSpeed;
        }
        else if ( getFanSpeed(test_number) == HighSpeed){
            FanStatus = Working;
            FanSpeedLevel = HighSpeed;
        }
        
        FanWorking = isFanWorking(FanStatus);
        (FanWorking == True) ? ( printf("The Fan is working. ") ) : 
                               ( printf("The Fan is NOT working !") ); //三元運算子
                               
        printFanSpeedLevel(FanSpeedLevel);
    }// for end
    
    return 0;
}

int getFanSpeed(int _test_number){
    // 隨意定義一個 TestArray 來產生測試的資料
    // 藉由 test_number 回傳一個轉速的值
    // 模擬我們藉由這個函式,透過底層的硬體等等的,得到的轉速
    int fanSpeed_TestArray[4] = {
        0,
        1000, // 1000 轉
        2000, // 2000 轉
        3000 // 3000 轉
    };
    return fanSpeed_TestArray[_test_number];
}

bool isFanWorking(Fan_Status_bool _FanStatus){
    switch (_FanStatus){
        case NonWorking:
            return False;
        case Working:
            return True;
    }
}

void printFanSpeedLevel(Fan_Speed_Level_Status _FanSpeedLevel){
    switch (_FanSpeedLevel){
        case NoSpeed:
            printf(" \r\n");// Fan is not working, print nothing
            break;
        case LowSpeed:
            printf(" The Fan Speed Level is LowSpeed\r\n");
            break;
        case MidSpeed:
            printf(" The Fan Speed Level is MidSpeed\r\n");
            break;
        case HighSpeed:
            printf(" The Fan Speed Level is HighSpeed\r\n");
            break;
    }
}









我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年8月31日 星期六

一起學 Python 114 : 使用 pyinstaller 庫打包為 exe 檔案

使用 pyinstaller 打包為 exe 檔案

若使用 pyinstaller -D '檔案位置'  則會將所須要的檔案全部包為一個資料夾


打包完成的資料會放置於  C:\Users\USER\dist



檔案大小為 565 MB




若單純使用 -F 則會生成一個單一的 .exe 檔案




檔案大小為 203 MB




我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

一起學 Python 113 : 使用 timeit 庫 計算 函式執行時間


from timeit import timeit
import numpy as np

def FuncA():
    a = np.arange(100000)
    b = np.arange(100000)
    c = a**2 + b**2
    return c
 
print( "Start to execute FuncA" )

# timeit ( '函數名稱' , 'from __main__ import 函數名稱', number = 執行次數)
t = timeit( 'FuncA()','from __main__ import FuncA', number = 5000 )

print( "FuncA takes " + str(t) + " msec\r\n" )

input()






我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年1月27日 星期日

《入門》寫程式Arduino教學 - 10 : 按鈕開關 以及 防機械彈跳

機械開關往往會有機械彈跳的問題

所產生的結果就是明明按鈕指有被按一下,但 Arduino 確認為按了很多下

這不是 Arduino 的問題,而是所有的微控制器因為其感測速度太快而產生

解決機械彈跳最簡單的辦法就是使用 debounce 函式庫

直接上程式碼


#include "Bounce.h"

#define Btn 2

Bounce bouncer = Bounce( Btn,40 ); // 若將 40 這個數字改大 debounce 效果會更好
// 但有可能造成開關變的不是很靈敏,建議設置 30~50 之間
int i = 0;

void setup() {
  Serial.begin(9600);
  pinMode(Btn,INPUT); //按鈕預設為低位觸發,也就是按下按鈕後讀值為 LOW 電壓
}

void loop() {
  
  if ( bouncer.update() && !bouncer.read()){  
      i++;
      Serial.println(i);
    
  }
  
}




我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《高階》寫程式Arduino教學 - 12:Arduino Uno 睡眠以及看門狗定時喚醒

程式碼於 Blogger 中難以呈現,請連結至 Github

實現功能 :

使 Arduino Uno 進入休眠模式,且每隔 24 秒鐘才醒來一次
可以大幅降低 Arduino Uno 的耗電量



我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年1月22日 星期二

NodeMCU 教學 - 17:WeMos D1 mini (NodeMCU) 配合 PT6961 小時鐘

此文用於分享程式碼給由 Facebook message 提問的呂先生


#include "ESP8266WiFi.h" 
#include "BlynkSimpleEsp8266.h"
#include "WidgetRTC.h"
#include "OasisLED.h"

// ===== 連線環境設置 =====
char ssid[] = "yourNetwork"; //  your network SSID (name)
char pass[] = "secretPassword";    // your network password (use for WPA, or use as key for WEP)
char auth[] = "........."; // 輸入 BLYNK 的 authkey

volatile byte h,m; //顯示時間用

// ===== 硬體腳位設置 =====
const int dataPin = D3;
const int clkPin = D5;
const int csPin = D6;

// ===== 副函式 Header =====
void WiFiConnect();
void getTime();
void timer_30000(); // 30 sec

// ===== 實體設置 =====
SimpleTimer   timer; //中斷定時器
WiFiClient    ESPclient;
WidgetRTC   rtc;
OasisLED ledDisplay = OasisLED(clkPin, csPin, dataPin);

void setup() { 
  ESP.wdtDisable();
  ESP.wdtEnable(WDTO_8S);
  
  ledDisplay.initialize(); //初使化
  ledDisplay.setSpinnerMode(SPIN_NONE);
  ledDisplay.setBrightness(0); //設定亮度(0~7) 0最暗 

  /* 連上 WiFi */
  WiFiConnect();  

  /* Blynk init */  
  Blynk.config(auth); //輸入Auth,顯示 BLYNK on Arduino 歡迎訊息
  Blynk.connect(); // 連線至 BLYNK ,成功後會顯示 Ready
  rtc.begin();
  while( !(minute()&&second()) )  {Blynk.run();} //等待時間同步
   
  timer_30000(); //LED 更新顯示時間
  timer.setInterval(30000L, timer_30000);  
}
 
void loop() {    
  timer.run();  
  Blynk.run();
}

/* =====================*/
// ===== timer_30000 ====
/* =====================*/
void timer_30000(){
  getTime();
}

/* =====================*/
// ===== timer_30000 ====
/* =====================*/
void getTime(){
  ledDisplay.reset();
  h = hour();
  m = minute();
  int real_time = 100*h + m;
  ledDisplay.setBrightness(0);
  ledDisplay.setValue(real_time);    
}

/* =====================*/
// ===== WiFiConnect ====
/* =====================*/
void WiFiConnect(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年1月19日 星期六

《進階※應用篇》寫程式Arduino教學 -13:0.96吋 OLED (128*64) 電壓錶

請先使用前篇文章的 I2C SCANNER 掃描一下 I2C 的 Address

接著需下載兩個 Library 如下連結

https://github.com/adafruit/Adafruit_SSD1306

https://github.com/adafruit/Adafruit-GFX-Library

解壓縮後丟到 C:\Program Files (x86)\Arduino\libraries 裡面即可

需注意我的 OLED 為 128*64 若你不是的話可能需要修改

Adafruit_SSD1306.h 中的第 #27-33 行 (假設你的是 96_16則要修改成以下)

// ONE of the following three lines must be #defined:
//#define SSD1306_128_64
//#define SSD1306_128_32 
#define SSD1306_96_16  ///< DEPRECATED: old way to specify 96x16 screen
// This establishes the screen dimensions in old Adafruit_SSD1306 sketches
// (NEW CODE SHOULD IGNORE THIS, USE THE CONSTRUCTORS THAT ACCEPT WIDTH
// AND HEIGHT ARGUMENTS).

若你跟我一樣是128_64,那這個部份就可以不用動了

程式碼如下

#include "spi.h"
#include "wire.h"
#include "adafruit_ssd1306.h"

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define ADJ_PIN A0
Adafruit_SSD1306 Display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

int r = 0;

void setup()   {
  Display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  
  Display.setTextColor(WHITE);  
  Display.display();
  delay (1000);
  Display.clearDisplay();
}


void loop() {
  r = analogRead(ADJ_PIN);

  Display.setTextSize(1);
  Display.setCursor(0, 0);
  Display.println("Voltage(mV)");
  
  Display.setTextSize(3);
  Display.setCursor(0, 32);
  Display.println(Format(r, 3, 1));

  Display.display();
  Display.clearDisplay();
}

String Format(double val, int dec, int dig ) {

  // this is my simple way of formatting a number
  // data = Format(number, digits, decimals) when needed

  int addpad = 0;
  char sbuf[20];
  String fdata = (dtostrf(val, dec, dig, sbuf));
  int slen = fdata.length();
  for ( addpad = 1; addpad <= dec + dig - slen; addpad++) {
    fdata = " " + fdata;
  }
  return (fdata);

}


結果如下






我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年1月14日 星期一

《進階※應用篇》寫程式Arduino教學 -11:Lora 長距無線傳輸 SX1278 1W 433MHZ 繼電器同步應用

經過基礎篇與應用篇後,我實作了一個繼電器同步的應用

事實上這個實作應用的可用之處很廣泛
畢竟繼電器這東西在工控領域已經無所不在了


會做這個應用事實上還有一個故事
當時我剛拿到 Lora 模組,正想說要做什麼應用時
有人找我來幫他解決一個問題

問題是這樣的 :

在一個山區有上下兩個水塔,兩個水塔之間直線距離約 1.5km
當上水塔的水位過低時,下水塔會啟動幫浦把水打上去
本來上下水塔之間是走地下電纜靠兩個控制器來探測浮球與控制抽水馬達
但貌似是地下電纜的線斷了,但因為在地底下,根本無法查出線斷在哪裡
所以就委託我做一個無線訊號傳輸的應用



這問題正好可以使用 Lora 來解決(經我實測,我這個模組在都市的傳輸距離大約 2km左右,理論上在山區較無遮蔽物的狀況下可以達到更遠的傳輸距離)


知道問題點在哪後,我就著手開始寫程式了

目標是 Lora 發射端使用 4 個乾接點(連接至上水塔控制器的浮球訊號),以及可用來做為強制啟動的一組指撥開關

Lora 接收端使用含有 4 個繼電器的繼電器模組

將 Lora 發射端的 4 個乾接點訊號,直接與 Lora 接收端的 4 個繼電器模組同步
(例如 :  Lora 發射端的 1 號乾接點被短接,代表上水塔的浮球下降到某個水位,此時同步觸發 Lora 接收端的 1 號繼電器模組,啟動下水塔的抽水馬達 )


以下給出程式碼,其註解已經把該交代的都交代了,就不再贅述
(程式碼中實際上為 5 個訊號的同步,這是因為我做到一半才突然想到我只需要 4 個訊號同步即可,但電路都焊了,就這麼將錯就錯吧)

發射端 ( ID: EA01 ; 波段: 13 )

請至 github連結 觀看 (因程式碼中有些寫法導致在 Blogger 中無法顯示)

接收端 ( ID: EA00 ; 波段: 13 )

#include "SoftwareSerial.h"

#define LED 13
#define output_1 7
#define output_2 8
#define output_3 9
#define output_4 10
#define output_5 11
#define AUX 5

SoftwareSerial mySerial(2, 3); // RX, TX
void waitAUX();
byte incoming2Byte[2]={0xFF,0xFF}; /* buffer */

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(AUX,INPUT);
  pinMode(LED,OUTPUT);
  pinMode(output_1,OUTPUT);
  pinMode(output_2,OUTPUT);
  pinMode(output_3,OUTPUT);
  pinMode(output_4,OUTPUT);
  pinMode(output_5,OUTPUT);
  /*因該繼電器模組為低電位觸發,為了不誤動作,在重開機時都先將繼電器關閉*/
  digitalWrite(output_1,HIGH);
  digitalWrite(output_2,HIGH);
  digitalWrite(output_3,HIGH);
  digitalWrite(output_4,HIGH);
  digitalWrite(output_5,HIGH);
  waitAUX();
  Serial.println("Start.");
}

void loop() {
  waitAUX();
  if (mySerial.available() > 0) {
    mySerial.readBytes(incoming2Byte, 2); /* 將 2bytes 資料餵入 buffer */   
    digitalWrite(LED,!digitalRead(LED));
    Serial.flush();
  }
  
  Serial.print("incoming2Byte[0] : ");
  Serial.print(incoming2Byte[0]); /* 第一個 byte 的數據 */
  Serial.print(" , incoming2Byte[1] : ");
  Serial.println(incoming2Byte[1]); /* 第二個 byte 的數據 */
  Serial.print("B1: "); Serial.println((~incoming2Byte[0]>>4)&0x01);
  Serial.print("B2: "); Serial.println((~incoming2Byte[0]>>3)&0x01);
  Serial.print("B3: "); Serial.println((~incoming2Byte[0]>>2)&0x01);
  Serial.print("B4: "); Serial.println((~incoming2Byte[0]>>1)&0x01);
  Serial.print("B5: "); Serial.println((~incoming2Byte[0]>>0)&0x01);
  digitalWrite(output_1, (~incoming2Byte[0]>>4)&0x01 );
  digitalWrite(output_2, (~incoming2Byte[0]>>3)&0x01 );
  digitalWrite(output_3, (~incoming2Byte[0]>>2)&0x01 );
  digitalWrite(output_4, (~incoming2Byte[0]>>1)&0x01 );
  digitalWrite(output_5, (~incoming2Byte[0]>>0)&0x01 );
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}






我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《進階※應用篇》寫程式Arduino教學 - 10:Lora 長距無線傳輸 SX1278 1W 433MHZ 進階 收發 2Bytes

將前篇的發送端程式碼修改為 "發送兩個 Bytes 數據的程式碼"

該程式碼中使用了 Serial.readBytes() 函式,並指定接收 2 Bytes

發送端(發送2 Bytes)

#include "SoftwareSerial.h"

#define LED 13
#define AUX 5

void waitAUX();

SoftwareSerial mySerial(2, 3); // RX, TX

byte incoming2Byte[2]={0xFF,0xFF}; /* buffer */

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(LED,OUTPUT);
  pinMode(AUX,INPUT);
  Serial.println("Start.");
}

void loop() {   
  waitAUX();
  if (mySerial.available() > 0) {   /*注意,這邊已改為 mySerial */
    digitalWrite(LED,!digitalRead(LED));
    mySerial.readBytes(incoming2Byte, 2); /* 將 2bytes 資料餵入 buffer */
    
    Serial.print("incoming2Byte[0] : ");
    Serial.println(incoming2Byte[0]); /* 第一個 byte 的數據 */
    Serial.print("incoming2Byte[1] : ");
    Serial.println(incoming2Byte[1]); /* 第二個 byte 的數據 */
    Serial.flush();
    mySerial.flush();    
  }
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}


查看 mySerial 的 Tx 端發送給 SX1278 的數據



接收端沿用之前的代碼如下

#include "SoftwareSerial.h"

#define AUX 5
#define LED 13

SoftwareSerial mySerial(2, 3); // RX, TX
void waitAUX();
byte incomingByte = 0;

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(AUX,INPUT);
  pinMode(LED,OUTPUT);
  waitAUX();
  Serial.println("Start.");
}

void loop() {
  if (mySerial.available() > 0) {
    incomingByte = mySerial.read(); 
    Serial.println(incomingByte); 
    digitalWrite(LED,!digitalRead(LED));
    Serial.flush();
  }    
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}


串列埠監看視窗

直接查看 SX1278 的 Tx 端口發送出來的數據



上方的接收端程式碼有個缺點,就是我們無法很簡易的將每一個 Byte 的資料獨立取出,這樣就失去了傳送多個 Bytes 的意義

故我修改了下程式碼,使其可以將 2Bytes 的資料獨立抓出來 (若有需要更多 Bytes 的獨立抓取,只要修改幾個小地方即可,在這我就不給出程式碼了)

改良版的接收端代碼(將收到的數據以 Byte 為單位獨立取出)

#include "SoftwareSerial.h"

#define AUX 5
#define LED 13

SoftwareSerial mySerial(2, 3); // RX, TX
void waitAUX();
byte incoming2Byte[2]={0x12,0x34}; /* buffer */

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(AUX,INPUT);
  pinMode(LED,OUTPUT);
  waitAUX();
  Serial.println("Start.");
}

void loop() {
  waitAUX();
  if (mySerial.available() > 0) {
    mySerial.readBytes(incoming2Byte, 2); /* 將 2bytes 資料餵入 buffer */
    Serial.print("incoming2Byte[0] : ");
    Serial.println(incoming2Byte[0]); /* 第一個 byte 的數據 */
    Serial.print("incoming2Byte[1] : ");
    Serial.println(incoming2Byte[1]); /* 第二個 byte 的數據 */

    digitalWrite(LED,!digitalRead(LED));
    Serial.flush();
  }    
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}


串列埠監看視窗



我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

《進階※應用篇》寫程式Arduino教學 - 09:Lora 長距無線傳輸 SX1278 1W 433MHZ 進階

此篇不同於基礎篇

額外使用了  SoftwareSerial.h 將 uart 通訊口獨立出來,以免跟 Arduino IDE 的監看視窗打架
並且將 "接收端的地址+波段" 與 "數據" 分隔開來

發射端代碼(同樣發射一個byte)

#include "SoftwareSerial.h"

#define AUX 5
#define LED 13

SoftwareSerial mySerial(2, 3); // RX, TX

const byte address[] = {0xEA,0x00,0x0D};
byte data = 0xBB; //預計傳送數據為 BB

void waitAUX();
void sendData1Byte (const byte* address, byte data);

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(AUX,INPUT);
  pinMode(LED,OUTPUT);
  waitAUX();
  Serial.println("Start.");
}

void loop() {
  pinMode(LED,!digitalRead(LED));
  sendData1Byte (address, data); //傳送 1 Byte 的數據
  delay(1500);
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}

// ===== sendData1Byte =====
void sendData1Byte (const byte* address, byte data){
  waitAUX();
  for(int i = 0; i < 3; i++){
    mySerial.write(address[i]);
    delay(2);
  }
  mySerial.write(data);
  delay(2);
  mySerial.flush();  
  waitAUX();
}



接收端代碼(亦改為使用SoftwareSerial.h)

#include "SoftwareSerial.h"

#define AUX 5
#define LED 13

SoftwareSerial mySerial(2, 3); // RX, TX
void waitAUX();
byte incomingByte = 0;

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(AUX,INPUT);
  pinMode(LED,OUTPUT);
  waitAUX();
  Serial.println("Start.");
}

void loop() {
  if (mySerial.available() > 0) {
    incomingByte = mySerial.read(); 
    Serial.println(incomingByte); (0xBB 十進制為 187)
    digitalWrite(LED,!digitalRead(LED));
    Serial.flush();
  }    
}

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}



接收端的串列埠監看視窗



接收端由 SX1278 Tx 端擷取到的數據



可以發現,接收端的 SX1278 接收到數據後,會直接僅將數據由 SX1278  Tx 發送出來,前面的"接收端的地址+波段" 並不會囊括在內


我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2019年1月12日 星期六

《進階※應用篇》寫程式Arduino教學 - 08:Lora 長距無線傳輸 SX1278 1W 433MHZ 基礎

先前使用過 RF 與 uart 無線傳輸後,一直想碰碰更長距離的傳輸
畢竟先前使用的無線傳輸其功率較小
頂多傳個 100m 就是極限了
甚至使用 RF 433MHz 只能無線傳輸個 5~6 米 ,而且拐個彎遇到水泥牆就斷線了
這讓我覺得很不便,如果不能長距離傳輸我何必弄的這麼麻煩呢?

Lora 是這幾年才出現的無線傳輸協議
其號稱可以傳輸到 km 等級的
於是我就到沒屋頂拍賣買了一組來玩玩

我購買的 Lora 模組規格為以下


商品型號:E32 (433T30D). (舊型號 E32 433-TTL-1W)

規格:

LoRa IC:SX1278
頻率:內定433MHZ (範圍 410 - 441 MHZ)
功率:30dbm (1W)
參考距離:8Km
發射電流:610mA (@30dbm)
接收電流:20mA
休眠電流:5uA
接收感度:-147dbm (空速 0.3 kbps)
通訊方式:展頻
價格 :單個 500 NT (需一次買兩個成一對)

以上的訊息是我從賣家的購買資訊中複製過來的
有興趣的可以在沒屋頂找找,很容易就能找到我是購買哪一家的
順到一提,我是搭配 6db 天線使用

剛買來的時候遇到了一些坑,所以就寫了這篇文分享一下最後的心得
先說結論,在空速 0.3kbps (也就是較慢的傳輸速率) 的情況下
直線距離可傳輸約 2.3 km 左右 (中間有隔許多建築物)
雖然不到賣家敘述的 8km (依賣家所述,8 km 要在空曠處才可以達到)
但這樣的傳輸距離我已經很滿意了

正文開始

剛買來這個 SX1278 Lora 模組的時候,需要先使用廠商自帶的軟體進行一下設定
因為我是成對買的,本來以為可以直接使用
但搞來搞去後發現廠商出廠時預設模組為 "廣播模式"(後述) 導致我一直失敗
後來設定為 "點對點模式"(後述) 就成功了

首先我們看看軟體的介面
與模組的溝通需額外使用一個 FT232 透過 Uart 讓電腦與模組進行連線
需注意的是,SX1278 模組的 I/O 可接受電壓為 3.3~3.7V
所以我在 FT232 的 Tx 端額外加上分壓電組降至約 3.4V 後才連接至 SX1278
電路圖我就省略不給了

硬體確定連接正確並打開該溝通的軟體後,選好 COM Port
先點選打開串口,接著點選讀取參數
若成功連接上 SX1278 的話就會跳出參數讀取成功的視窗

於參數設定模式(休眠模式)時 : SX1278  的 M0 與 M1 短接,並且使用 10K 歐姆的上拉電阻至 SX1278 的 Vcc


我們來看看有什麼東西可以進行設置



波特率 : 即為 FT232 <-> SX1278 之間的 Baudrate ,預設為 9600

奇偶校驗 : 這邊不用動,就選 8N1 就行了

空中速率 : 最快可以達到 19.2 Kbps ,理論上來說速度越快傳輸的距離會越短,且可能有數據丟失的狀況會發生。在我的應用中我想測試這個模組能傳輸的最遠極限距離為多少,所以我選擇最慢的 0.3 kbps

發射功率 : 我選最大的 30dbm

前向糾錯 : 這就是 Lora 協議一個很特殊的地方,基本上這是要打開的。至於是什麼意思,隨意 Google 一下就有,在這我就不多說了

傳輸方式 : SX1278 的傳輸方式分為兩種

第一為透傳模式(也就是廣播模式) : 這個模式可以使多個 SX1278 模組成為一個網路,只要所有 SX1278 的頻率通道都設置為相同,在該通道(波段)的所有 SX1278 都可以收到訊息

第二為定點模式(也就是點對點傳輸,或更簡易理解為特定的兩個 SX1278 之間的傳輸) : 由於我就只有買兩個 SX1278 ,所以我實際上就是需要點對點傳輸

Hint : 廠商出廠預設值為透傳模式,這也是我一開始為什麼卡很久無法成功的原因。設置為定點模式後就解決了




傳輸的規格

1. 目標地址(也就是接收方的模塊地址),以上圖舉例,若我們想要使用另外一個 SX1278 傳輸數據到上圖中的 SX1278 ,則我們就要在發送端先發送 0xea 再發送 0x01

2. 只發送目標地址是不夠的,我們還要接著發送目標信道(也就是上圖中的頻率信道,白話文就是"波段")

同樣以上圖舉例,若頻率信道為 13 ,則表示我們使用的波段為 410Mhz + 13*1Mhz = 423 Mhz 。將其轉換為十六進制為 0x0D

3. 最後我們再接著發送數據


總結舉例  : 若我們要將數據 "0xAA" 這個值發送到上圖的 SX1278 模組 (地址:ea01,通道:13) 則我們要連續發送 「四個數值」,分別為

0xea, 0x01, 0x0D, 0xAA

這樣上圖中的 SX1278  就會收到 0xAA 的數據





以下我給出一個發送端很簡單的 Arduino 程式碼(傳送接收方的地址 + 頻段 + 1byte的數據(0xAA))

#define LED 13
#define AUX 2

/* 含接收方的地址 + 頻段 + 1byte的數據(0xAA) */
/* 若此處宣告為 char 陣列,則使用 Serial.print 函數來發送 */
/* 若此處宣告為 byte 陣列,則使用 Serial.write 函數來發送 */
const char data[] = {0xEA,0x01,0x0D,0xAA};
//const byte data[] = {0xEA,0x01,0x0D,0xAA};

// ===== 等待 Lora 晶片不繁忙 =====
void waitAUX(){
  while(!digitalRead(AUX));
  delay(10);
}

// ===== 傳送資料 =====
void sendDataByHexArray (const char* data, int dataSIZE){
  waitAUX();  
  for(int i = 0; i < dataSIZE; i++){
    Serial.print(data[i]);
    //Serial.write(data[i]);
    delay(2);
  }
  Serial.flush();  
  waitAUX();
}

void setup() {
  Serial.begin(9600);
  pinMode(LED,OUTPUT);
}

void loop() {
  pinMode(LED,!digitalRead(LED));
  sendDataByHexArray (data, ( sizeof(data)/sizeof(data[0]) ) );
  delay(2000); //適時的delay
}


接收端

#define LED 13

byte incomingByte = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED,OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read(); //接收到數據0xAA    
    Serial.print(incomingByte); //此函數預設會在Arduino IDE 的監看埠視窗列印出十進制,故得 "170"
    digitalWrite(LED,!digitalRead(LED));
    Serial.flush();
  }    
}


注意,因我直接使用 Arduino 的 pin0 & pin1 接腳作為與 SX1278 的通訊接腳
所以你會發現,接收端的 Serial.print(incomingByte); 語句實際上會讓接收端的 SX1278 再發射出數據

只不過因為格式不符,所以只是白發送而已

較好的方式是使用 SoftwareSerial.h (使用 I/O 腳位模擬 Uart 通訊),來與 SX1278 溝通會更好一些 (於下篇文章中使用)


補充 : SX1278 與 Arduino 之間的硬體接線

1. SX1278 與 Arduino 的 Rx 與 Tx 對接,並且需注意 SX1278 的 I/O 限制電壓為 3.3~3.7v ,所以由 Arduino Tx 送出來的數據需要分壓後才能餵進  SX1278

2.  於參數設定模式(休眠模式)時 : SX1278  的 M0 與 M1 短接,並且使用 10K 歐姆的上拉電阻至 SX1278 的 Vcc

3. 於工作模式時 : SX1278  的 M0 與 M1 短接並直接接至 GND

4. Aux 需連接到 Arduino 的某個接腳,並且設置為 INPUT Mode 。藉由 Aux 可以判斷 SX1278 是否忙碌中




我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片




閱讀更多 »

2019年1月10日 星期四

《高階》寫程式Arduino教學 - 11:Arduino Nano 外部中斷 以及 Serial.print 仿格式化輸出


/* 此例中我使用的 button 為常閉型(若設置為pull-up模式則在按鈕未按下時為 LOW)

#define button_pin 2

volatile  unsigned long current_time = 0; //單位:msec
volatile  unsigned long duration_time = 0; //單位:sec
volatile  unsigned int start_flag = 0;
volatile  unsigned long last_interrupt_time = 0;
volatile  unsigned long interrupt_time = 0;

void buttonPressed(){

  /* 外部中斷 + debounce + 按鈕釋放時觸發 */
  interrupt_time = millis(); 
  if ( (interrupt_time - last_interrupt_time > 300)  && !digitalRead(button_pin) ){
    /* ----- 主體開始 ----- */
    if (!start_flag){
      Serial.println("Start");
      start_flag = 1;
      current_time = millis();
    }
    else{
      duration_time = (millis() - current_time)/1000;
      if ( duration_time <= 60 ){
        // 仿傳統 C 語言的格式化輸出
        String text = String("") + "duration_time : " + duration_time + "sec";
        Serial.println(text);
      }
      else{
        String text = String("") + "duration_time : " 
        + duration_time/60 + "min " + duration_time%60 + "sec";
        Serial.println(text);
      }
      current_time = millis();
    }
    /* ----- 主體結束 ----- */
    
    last_interrupt_time = interrupt_time;
  }
}

void setup()
{
  Serial.begin(115200);
  pinMode(button_pin,INPUT_PULLUP);
  attachInterrupt(0, buttonPressed, CHANGE); //外部中斷0 = pin2
}
void loop() {
}





我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

2018年12月29日 星期六

NodeMCU 教學 - 17:ESP8266 看門狗 WatchDog 關閉 ESP.wdtDisable() 使用法則

使用  ESP.wdtDisable() 關閉 Watchdog 指令的情況下,其 ESP8266 仍然會在約 8.35 秒左右觸發 Watchdog ,而這個 Watchdog 屬於 Hardware watchdog

此時版子會卡住,無法自動重啟,報出如下 圖(一) 訊息

ets Jan  8 2013,rst cause:4, boot mode:(1,6)

wdt reset 

接著,按了板上的 Reset 按鈕,仍然過了 8.35 秒觸發 Hardware watchdog 報出訊息

ets Jan  8 2013,rst cause:4, boot mode:(1,6)

wdt reset 

但此時版子就會自動重啟了,如下 圖(二)


圖(一)

圖(二)

不使用  ESP.wdtDisable() 關閉 Watchdog 指令的情況下,其 ESP8266 (我使用Wemos D1 mini 進行測試) 會在約 3.2 秒左右觸發 Software watchdog

並報出如下圖訊息

ets Jan  8 2013,rst cause:2, boot mode:(1,6)

ets Jan  8 2013,rst cause:4, boot mode:(1,6)

wdt reset 




結論

1.
ESP.wdtDisable() 的看門狗關閉指令針對的是 Software watchdog

ets Jan  8 2013,rst cause:2, boot mode:(1,6) 是由 Software watchdog 所產生

ets Jan  8 2013,rst cause:4, boot mode:(1,6) 是由 Hardware watchdog 所產生

2.
如果在版子上電的一開始我們沒有手動按一下 Reset 按鈕,之後若觸發 Hardware watchdog ,版子就會卡住

解決辦法就是記得在版子上電的一開始手動按一下 Reset 按鈕

3.
若觸發了 Software watchdog 版子會卡一下( 約 5.15 秒),然後觸發 Hardware watchdog

若沒有在版子上電的一開始我們沒有手動按一下 Reset 按鈕,版子就會當機卡住

4.
縱使你使用 ESP.wdtDisable() 關閉 Software watchdog ,但 Hardware watchdog 仍舊沒被關閉,所以你必須在程式碼中的各個角落加入 ESP.wdtFeed(); 來避免看門狗被觸發

補充測試結論

1.
在沒有使用 ESP.wdtDisable(); 來關閉看門狗的前提下

(或使用了 ESP.wdtDisable(); 後,有再使用 ESP.wdtEnable(); 的開啟看門狗指令)

delay(0); 以及 yield();    效用等同 ESP.wdtFeed();

反之,若使用了 ESP.wdtDisable(); 則只能用 ESP.wdtFeed(); 來避免看門狗被觸發



我的 Youtube 頻道,一定要訂閱
我將定期推出程式語言的新手教學影片


閱讀更多 »

↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引