2015/9/7

iw指令連線操作記錄

以下範例假定無線網卡裝置名稱為wlan0(通常會是這名字),若不知道可以透過ifconfig -a指令查詢無線網卡裝置名稱。

確認無線裝置連線狀態
# iw wlan0 link
Not connected.
如果沒有任何連線,則會顯示Not connected,否則會顯示連線的相關資訊

掃描鄰近的wifi廣播訊號
# iw wlan0 scan

連線到WPA/WPA2加密的無線網路
# wpa_passphrase APPLE >> /tmp/wifi.conf
1234509876
APPLE是無線網路的SSID資訊,輸入上述指令會停住等待使用者輸入連線密碼,1234509876即為設定的密碼,輸入完後按下Enter即會自動產生連線相關文件
# reading passphrase from stdin
network={
 ssid="APPLE"
 #psk="1234509876"
 psk=a36065d029db325508281a51a19f8fff1c389c11129a74b6c6d3a25200cae713
}
連線指令
# wpa_supplicant -B -i wlan0 -c /tmp/wifi.conf
若沒跳出任何錯誤訊息應該就完成連線了
這時可以查詢連線狀態
# iw wlan0 link
Connected to 36:a3:95:d7:a7:55 (on wlan0)
 SSID: APPLE
 freq: 2437
 RX: 59639 bytes (440 packets)
 TX: 12188 bytes (76 packets)
 signal: -58 dBm
 tx bitrate: 65.0 MBit/s MCS 7

 bss flags: short-preamble short-slot-time
 dtim period: 3
 beacon int: 100

要求DHCP伺服器配發動態IP
# dhclient wlan0

2015/7/20

在Linux系統上製作Windows的USB安裝隨身碟(EFI支援的GPT格式)

不論在Linux或是Windows上要製作Linux安裝隨身碟都有現成套件可以輕易完成(如UNetbootin),但反過來要在Linux系統上製作Windows安裝隨身碟就沒這麼容易了,但也只需要幾個步驟就可以了!
目前還是EFI跟Legacy BIOS的接棒時期,安裝隨身碟也就分為EFI支援的GPT格式跟Legacy BIOS的MBR格式,這篇文章方法僅適用GPT格式。

先安裝gparted套件對隨身碟進行磁區分割
sudo apt-get install gparted
sudo gparted
  1. 在右上角下拉式選單中,選擇隨身碟裝置。
  2. 卸載隨身碟裝置
    功能列選單:Partition > Unmount
  3. 建立Partition表格
    功能列選單:Device > Create Partition Table ...
    選擇gpt
  4. 建立分割區
    功能列選單:Partition > New
    選擇fat32
  5. 儲存變更
    Ctrl + Enter
  6. 關閉gparted
掛載windows安裝印象檔(*.iso)並將內容複製到隨身碟
sudo mkdir /mnt/iso /mnt/usb
sudo mount *.iso /mnt/iso
sudo mount /dev/sdXY /mnt/usb
sudo cp -R /mnt/iso/* /mnt/usb

完成上述步驟後就可以重新開機,並將開機裝置指定到隨身碟,就會進入Windows安裝界面了。目前驗證過Windows7(64-bits)與Windows8(64-bits)均能成功進入安裝畫面。

2015/7/16

【治具】RS232 迴路測試每根Pin腳功能

RS232是個歷久不衰的訊號通訊協議,雖然在主流電腦上已經很難看見其蹤跡,但在伺服器或是嵌入式裝置乃至於近年很夯的物連網裝置都很容易見到,這篇文章主要記錄如何設計硬體治具並撰寫對應測試程式。

首先可以先參考RS232每根Pin腳的功能用途,網路上有大量文章介紹,就不再贅述,可以參考連結:Ethernut RS-232 Primer

先看做好的治具成品圖

治具硬體Rework
  1. 使用4.7kOhm電阻將TxD與RxD串接起來,如果只需要驗證資料傳輸只需做這部分重工即可,大部分情況下(未使用Hardware Flow Control)其他Pin腳都不會用到。
  2. 使用4.7kOhm電阻將DCD與DSR串接起來。
  3. 將DSR跟DTR直接短路串接。
  4. 使用4.7kOhm電阻將RI與CTS串接起來。
  5. 將CTS跟RTS直接短路串接。
測試概念
  1. 從TX發送資料,硬體TxD與RxD已經串接,因此會收到送出資料,確認收到資料並比對資料內容即可確認TxD與RxD腳位功能是否正常。
  2. 硬體RTS與CTS及RI串接,因此設置/重置RTS訊號,再讀取CTS與RI訊號即可確認這三根腳位功能是否正常。
  3. 硬體DTR與DSR及DCD串接,因此設置/重置DTR訊號,再讀取DSR與DCD訊號即可確認這三根腳位功能是否正常。
程式碼實做
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// File name: uart.c
// gcc -o uart uart.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

int main (int argc, char *argv[])
{
 int ret = 0;
 int len;
 unsigned char buf[16] = {0xAA, 0x55, 0x00, 0xFF,
    0x5A, 0xA5, 0xF0, 0x0F,
    0x01, 0x02, 0x04, 0x08,
    0x10, 0x20, 0x40, 0x80};
 unsigned char buf_rev[16];
 int fd;
 FILE *fp = NULL;
 int status;
 struct termios tio;
 
 if (argc <= 1) {
  fprintf(stderr, "Usage: %s <dev>\n", argv[0]);
  ret = 255;
  goto end;
 }

 fp = fopen (argv[1], "w+");
 if (fp == NULL) {
  puts ("Fail to open device");
  ret = 255;
  goto end;
 }
 
 printf ("+ TX: ");
 fflush (fp);
 len = fwrite (buf, sizeof (char), 16, fp);
 fflush (fp);
 if (len < 16) {
  puts ("Fail to transfer data");
  ret = 255;
  goto clean;
 }
 puts ("Pass");

 printf ("+ RX: ");
 len = fread (buf_rev, sizeof (char), 16, fp);
 if (len < 16) {
  puts ("Fail to receive data");
  ret = 255;
  goto clean;
 } else if (memcmp (buf, buf_rev, 16) != 0) {
  puts ("Check data fail");
  ret = 255;
  goto clean;
 }
 puts ("Pass");
 fclose (fp);
 fp = NULL;

 if ( (fd = open (argv[1], O_RDWR)) < 0 ) {
  puts ("Fail to open device");
  ret = 255;
  goto end;
 }

 tcgetattr(fd, &tio);
 printf ("RTS -> CTS - RI: ");
 status |= TIOCM_RTS;
 ioctl(fd, TIOCMSET, &status);
 ioctl(fd, TIOCMGET, &status);
 ret = status & (TIOCM_CTS | TIOCM_RNG); // Should be 0xA0

 status &= ~TIOCM_RTS;
 ioctl(fd, TIOCMSET, &status);
 ioctl(fd, TIOCMGET, &status);
 if ( (status & (TIOCM_CTS | TIOCM_RNG)) || ret != 0xA0) { // Status should be 0x00, false
  puts ("FAIL");
  if (ret == 0x0)
   puts ("  --> Case1: RTS fail; Case2: CTR & RI fail");
  else if (ret == 0x80)
   puts ("  --> CTS fail");
  else if (ret == 0x20)
   puts ("  --> RI fail");
  else
   puts ("  -> RTS fail");
  ret = 6;
  goto clean;
 } else {
  puts ("PASS");
  ret = 0;
 }

 printf ("DTR -> DSR - DCD: ");
 status |= TIOCM_DTR;
 ioctl(fd, TIOCMSET, &status);
 ioctl(fd, TIOCMGET, &status);
 ret = status & (TIOCM_DSR | TIOCM_CAR); // Should be 0x140
 status &= ~TIOCM_DTR;
 ioctl(fd, TIOCMSET, &status);
 ioctl(fd, TIOCMGET, &status);
 if ( (status & (TIOCM_DSR | TIOCM_CAR)) || ret != 0x140) { // Status should be 0x00, false
  puts ("FAIL");
  if (ret == 0x0)
   puts ("  --> Case1: DTR fail; Case2: DSR & DCD fail");
  else if (ret == 0x100)
   puts ("  --> DCD fail");
  else if (ret == 0x40)
   puts ("  --> DSR fail");
  else
   puts ("  -> RTS fail");
  ret = 7;
  goto clean;
 } else {
  puts ("PASS");
  ret = 0;
 }

clean:
 if (fp)
  fclose (fp);
 if (fd > 0)
  close (fd);
end:
 printf("Return Code: %d\n", ret);
 return ret;
}

參考連結

2015/7/8

Trace shell script的小技巧

參考上圖,有三個script,先執行test1.sh,會帶起test2.sh;test2.sh會再帶起test3.sh,如果從test1.sh開始了解程式架構,很容易就可以得知彼此之間的執行關係;但有時候線頭只抓到test2.sh或是test3.sh時就麻煩了。
  在Linux下可以使用bash maintain的$PPID變數再搭配ps指令就可以慢慢推敲出script file的呼叫關係。

直接看範例:
#!/bin/sh
# file: test1.sh

echo $0
echo "Execute by `ps -o comm -h --pid $PPID`"
echo ===========
./test2.sh

#!/bin/sh
# file: test2.sh

echo $0
echo "Execute by `ps -o comm -h --pid $PPID`"
echo ===========
./test3.sh

#!/bin/sh
# file: test3.sh

echo $0
echo "Execute by `ps -o comm -h --pid $PPID`"
echo ===========

執行結果:
./test1.sh
Execute by bash
===========
./test2.sh
Execute by test1.sh
===========
./test3.sh
Execute by test2.sh
===========

2015/7/7

更改Shell輸出文字的顏色、底線、粗體等控制

要在Shell輸出文字中有顏色改變、底線與粗體效果,需要用到如下的控制碼:
printf "'\033[0;31m'Hello'\033[0m'"
會在螢幕上顯示紅色的Hello,但這樣要記住所有效果的代碼也不是辦法,可以將每種效果定義為一種變數,如此要調用就會簡單多了,如下可以達到相同效果:
Red='\033[0;31m'
NC='\033[0m'

printf "${Red}Hello${NC}"

完整的效果定義
#!/bin/bash
# File: color.sh

# Regular Colors
Black='\033[0;30m'
Red='\033[0;31m'
Green='\033[0;32m'
Yellow='\033[0;33m'
#Orange='\033[0;33m'
Blue='\033[0;34m'
Purple='\033[0;35m'
Cyan='\033[0;36m'
White='\033[0;37m'

# Bold
BBlack='\033[1;30m'
BRed='\033[1;31m'
BGreen='\033[1;32m'
BYellow='\033[1;33m'
BBlue='\033[1;34m'
BPurple='\033[1;35m'
BCyan='\033[1;36m'
BWhite='\033[1;37m'

# Underline
UBlack='\033[4;30m'
URed='\033[4;31m'
UGreen='\033[4;32m'
UYellow='\033[4;33m'
UBlue='\033[4;34m'
UPurple='\033[4;35m'
UCyan='\033[4;36m'
UWhite='\033[4;37m'

# Background
GBlack='\033[40m'
GRed='\033[41m'
GGreen='\033[42m'
GYellow='\033[43m'
GBlue='\033[44m'
GPurple='\033[45m'
GCyan='\033[46m'
GWhite='\033[47m'

# High Intensity
IBlack='\033[0;90m'
IRed='\033[0;91m'
IGreen='\033[0;92m'
IYellow='\033[0;93m'
IBlue='\033[0;94m'
IPurple='\033[0;95m'
ICyan='\033[0;96m'
IWhite='\033[0;97m'

# Bold High Intensity
BIBlack='\033[1;90m'
BIRed='\033[1;91m'
BIGreen='\033[1;92m'
BIYellow='\033[1;93m'
BIBlue='\033[1;94m'
BIPurple='\033[1;95m'
BICyan='\033[1;96m'
BIWhite='\033[1;97m'

# High Intensity Background
GIBlack='\033[0;100m'
GIRed='\033[0;101m'
GIGreen='\033[0;102m'
GIYellow='\033[0;103m'
GIBlue='\033[0;104m'
GIPurple='\033[0;105m'
GICyan='\033[0;106m'
GIWhite='\033[0;107m'

LightGray='\033[0;37m'
DarkGray='\033[1;30m'
LightRed='\033[1;31m'
LightGreen='\033[1;32m'
Yellow='\033[1;33m'
LightBlue='\033[1;34m'
LightPurple='\033[1;35'
LightCyan='\033[1;36'

NC='\033[0m'

效果Demo
#!/bin/bash
# File: color_demo.sh
. color.sh

printf "${Black}Black${NC}\n"
printf "${Red}Red${NC}\n"
printf "${Green}Green${NC}\n"
printf "${Yellow}Yellow${NC}\n"
printf "${Blue}Blue${NC}\n"
printf "${Purple}Purple${NC}\n"
printf "${Cyan}Cyan${NC}\n"
printf "${White}White${NC}\n"

printf "${BBlack}BBlack${NC}\n"
printf "${BRed}BRed${NC}\n"
printf "${BGreen}BGreen${NC}\n"
printf "${BYellow}BYellow${NC}\n"
printf "${BBlue}BBlue${NC}\n"
printf "${BPurple}BPurple${NC}\n"
printf "${BCyan}BCyan${NC}\n"
printf "${BWhite}BWhite${NC}\n"

printf "${UBlack}UBlack${NC}\n"
printf "${URed}URed${NC}\n"
printf "${UGreen}UGreen${NC}\n"
printf "${UYellow}UYellow${NC}\n"
printf "${UBlue}UBlue${NC}\n"
printf "${UPurple}UPurple${NC}\n"
printf "${UCyan}UCyan${NC}\n"
printf "${UWhite}UWhite${NC}\n"

printf "${GBlack}GBlack${NC}\n"
printf "${GRed}GRed${NC}\n"
printf "${GGreen}GGreen${NC}\n"
printf "${GYellow}GYellow${NC}\n"
printf "${GBlue}GBlue${NC}\n"
printf "${GPurple}GPurple${NC}\n"
printf "${GCyan}GCyan${NC}\n"
printf "${GWhite}GWhite${NC}\n"

printf "${IBlack}IBlack${NC}\n"
printf "${IRed}IRed${NC}\n"
printf "${IGreen}IGreen${NC}\n"
printf "${IYellow}IYellow${NC}\n"
printf "${IBlue}IBlue${NC}\n"
printf "${IPurple}IPurple${NC}\n"
printf "${ICyan}ICyan${NC}\n"
printf "${IWhite}IWhite${NC}\n"

printf "${BIBlack}BIBlack${NC}\n"
printf "${BIRed}BIRed${NC}\n"
printf "${BIGreen}BIGreen${NC}\n"
printf "${BIYellow}BIYellow${NC}\n"
printf "${BIBlue}BIBlue${NC}\n"
printf "${BIPurple}BIPurple${NC}\n"
printf "${BICyan}BICyan${NC}\n"
printf "${BIWhite}BIWhite${NC}\n"

printf "${GIBlack}GIBlack${NC}\n"
printf "${GIRed}GIRed${NC}\n"
printf "${GIGreen}GIGreen${NC}\n"
printf "${GIYellow}GIYellow${NC}\n"
printf "${GIBlue}GIBlue${NC}\n"
printf "${GIPurple}GIPurple${NC}\n"
printf "${GICyan}GICyan${NC}\n"
printf "${GIWhite}GIWhite${NC}\n"
呈現結果

讓ssh連線不用手動輸入密碼(自動登入)

最近專案有個應用:在客戶端透過ssh自動連線到伺服器並下達命令,卻遇到了需要手動輸入密碼的問題,發現ssh有提供公私鑰機制可以免除每次登入都需要輸入密碼,設定步驟也很簡單:
  1. 產生金鑰:在客戶端執行
    ssh-keygen -t rsa -b 2048
    
    過程中會出現Enter passphrase提示輸入訊息,直接按Enter跳過即可
  2. 將產生的公鑰內容上傳到伺服器的~/.ssh/authorized_keys檔案
    ssh-copy-id user_id@server_ip
    
只要完成上述兩個步驟就完成了,步驟2的user_id需替換成遠端連線的帳號、server_ip則是遠端連線的ip,底下稍微做一下說明:
  • 步驟1會產生一組本機的公鑰與私鑰,執行完後可以在~/.ssh目錄下看到兩個檔案
    • id_rsa:私鑰,之後連線會使用這私鑰進行演算後傳送到遠端進行驗證
    • id_rsa.pub:公鑰,內容會儲存在遠端的~/.ssh/authorized_keys檔案內,客戶端傳來的驗證資訊會以這資訊進行驗證
  • 步驟2的ssh-copy-id其實是一隻script(/usr/bin/ssh-copy-id),功能就是把id_rsa.pub內容幫忙存到遠端的~/.ssh/authorized_keys檔案內。

2015/6/1

【QEMU】在X86 PC上執行其他平台(如ARM)程式

情境:要在X86 PC上透過QEMU模擬器執行ARM的程式。

先透過file指令確認執行程式的格式內容為ARM平台所用
$ file hello
hello: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=d7efe6f9528e036c383501d6fc0dd1ca6fc880b1, not stripped

qemu最後面加上要執行的程式即可,中間options視需求加入適當的參數
usage: qemu-arm [options] program [arguments...]

先直接執行看看
$ qemu-arm ./hello
/lib/ld-linux.so.3: No such file or directory

qemu不知道去哪裡載入loader,手動指定路徑後再執行看看
$ qemu-arm /usr/arm-linux-gnueabi/lib/ld-2.19.so ./hello
./hello: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

這時loader需要載入相關library,手動指定動態鏈結檔目錄再執行看看
$ qemu-arm /usr/arm-linux-gnueabi/lib/ld-2.19.so --library-path /usr/arm-linux-gnueabi/lib ./hello
Hello World

這時應該就可以看到正確執行結果了!

2015/4/27

【Ubuntu】架設PXE Server

PXE開機運作原理是Bootloader(PC來說就是BIOS)在開機時動態要求分配IP,在取得IP資訊同時也會收到下一個伺服器的位址與該讀取的檔案資訊,接著透過TFTP服務取得開機核心與檔案系統。所以一台伺服器要提供PXE功能,至少要有DHCP與TFTP伺服器功能,首先安裝這兩個套件:
sudo apt-get install isc-dhcp-server
apt-get install tftpd-hpa inetutils-inetd

指定提供DHCP服務的網路卡
sudo vi /etc/default/isc-dhcp-server
將interface指定為eth0
INTERFACES="eth0"

設定DHCP服務
sudo vi /etc/dhcp/dhcpd.conf

# option definitions common to all supported networks...
#option domain-name "example.org";
#option domain-name-servers ns1.example.org, ns2.example.org;

subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.100 192.168.0.253;
  option routers 192.168.0.254;
  option broadcast-address 192.168.0.255;
}
allow booting;
allow bootp;
option option-128 code 128 = string;
option option-129 code 129 = text;
next-server 192.168.0.254;
filename "pxelinux.0";

default-lease-time 600;
max-lease-time 7200;
若有domain name資訊則填入正確資訊,否則將相關兩的兩行敘述註解起來,否則啟動DHCP服務可能會帶不起來。這邊設定IP分配會從192.168.0.100到192.168.0.253,並指定client端取得IP資訊後接著要向192.168.0.254伺服器要求檔案pxelinux.0。
完成設定後重新啟動DHCP服務
sudo service isc-dhcp-server restart

修改tftp設定
sudo vi /etc/default/tftpd-hpa
增加敘述:
RUN_DAEMON="yes"
修改inet.conf
sudo vi /etc/inetd.conf
增加敘述
tftp dgram udp wait root /usr/sbin/in.tftpd /usr/sbin/in.tftpd -s /var/lib/tftpboot

重新啟動tftp服務
sudo service tftpd-hpa restart

到這邊PXE服務就算設定完成了,這時在client端選擇網路開機,應該會成功取得IP配置,但會卡在取得檔案階段,底下說明使用ubuntu server的安裝光碟裡面提供的netboot檔案,讓client可以透過PXE服務開啟安裝程式。

掛載安裝印象檔,並將需要的檔案複製到tftp目錄即可
sudo mount ubuntu-14.10-server-amd64.iso /mnt
sudo cp -avr /mnt/install/netboot/* /var/lib/tftpboot/

這時client在選擇網路開機,就可以進到ubuntu安裝畫面了!

2015/4/23

【樹莓派】寫Driver控制GPIO

  控制RPi的GPIO方法很多,在User Space下可以透過讀寫/dev/mem或是/sys/class/gpio來控制輸出與讀取狀態;在Kernel Space下就要自己寫一個Driver(或稱為核心模組)來實現了!另外有高手寫了WiringPi讓大家可以簡單透過python來實現IO控制(包括I2C與SPI),也提供API讓C語言之類的程式進行調用。
  這篇文章提供一個簡單的範例,示範如何寫一個驅動程式控制RPi的GPIO。
  首先查看電路圖(Schematic),挑選GPIO_GEN0來連接LED燈正極,並將LED負極接到旁邊的地(Ground,Pin9),如此一來把Pin11設為1就會點亮LED燈,設為0就會讓LED熄滅。

  上圖的GPIO_GEN0是Pin11,但這個Pin11指的是P1這個連接頭(Header)的編號,實際上我們要控制的是BCM2835(RPi的ARM核心)接腳,所以得在電路圖上找尋GPIO_GEN0在核心上對應的GPIO編號,查看下圖得知是GPIO17
確定好要控制的GPIO編號後,就可以來撰寫驅動程式了,直接看Code
rpi.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

#define DRIVER_AUTHOR    "YS Wang <yaushung AT gmail.com>"
#define DRIVER_DESC    "RPi GPIO Output Demo"
#define GPIO0_PIN    17
#define GPIO0_DESC    "GPIO0"
#define GPIO0_DEVICE_DESC   "GPIO17Output"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

int rpi_init(void) {
 printk(KERN_NOTICE "Hello, RPi !\n");
 
 if (gpio_request(GPIO0_PIN, GPIO0_DESC)) {
  printk("[%s] Request fail\n", GPIO0_DESC);
  return -EIO;
 }

 printk(KERN_ALERT "[%s] Request Pin%d ... Success.\n",
  GPIO0_DESC, GPIO0_PIN);

 gpio_direction_output(GPIO0_PIN, 1);

 return 0;
}

void rpi_exit(void) {
 printk(KERN_ALERT "Goodbye, RPi\n");
 gpio_direction_output(GPIO0_PIN, 0);
 gpio_free(GPIO0_PIN);
 return;
}

module_init(rpi_init);
module_exit(rpi_exit);
Makefile
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

CFILES  = rpi.c

obj-m += rpi.o
sample-objs := $(CFILES:.c=.o)

all:
 make -C /home/acos/rpi/linux M=$(PWD) modules

clean:
 make -C /home/acos/rpi/linux M=$(PWD) clean

直接下make(make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-)指令就會產生rpi.ko模組檔案,接著把檔案複製到RPi上執行
sudo insmod rpi.ko      # 模組載入,這時會看到LED點亮
sudo rmmod rpi      # 模組卸除,這時會看到LED熄滅

編譯環境設置可以參考這篇文章

2015/4/20

【樹莓派】架設DHCP Server

首先安裝DHCP Server套件
sudo apt-get install isc-dhcp-server
安裝完畢系統會嘗試帶起服務,但設定都還沒弄好所以會看到錯誤訊息,忽略即可。
[FAIL] Starting ISC DHCP server: dhcpd[....] check syslog for diagnostics. ... failed!
 failed!
invoke-rc.d: initscript isc-dhcp-server, action "start" failed.

設定eth0網卡IP為192.168.0.1,修改/etc/network/interfaces文件
#iface eth0 inet dhcp
allow-hotplug eth0
iface eth0 inet static
  address 192.168.0.1
  netmask 255.255.255.0
  gateway 192.168.0.1

設定dhcp組態文件/etc/dhcp/dhcpd.conf,文件中有很多範例可以參考,依據自己需求挑一個合適的範本進行設定,這裡讓Server會配發192.168.0.200到192.168.0.253之間的IP給Client端
subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.200 192.168.0.253;
  option routers 192.168.0.1;
  option broadcast-address 192.168.0.255;
  default-lease-time 600;
  max-lease-time 7200;
  option domain-name-servers 168.95.1.1;
}

編輯/etc/default/isc-dhcp-server文件,指定提供DHCP服務的網卡,找到INTERFACES敘述,填上eth0
# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#       Separate multiple interfaces with spaces, e.g. "eth0 eth1".
INTERFACES="eth0"

最後重新啟動服務,看到ok就表示服務正常啟動了
sudo service isc-dhcp-server restart
[ ok ] Stopping ISC DHCP server: dhcpd.
[ ok ] Starting ISC DHCP server: dhcpd.

這時接上一台電腦應該就會自動獲取RPi指派的IP了,可以搭配【無線網路轉送有線網路橋接】設定封包轉送服務,這樣RPi就可以當作一台簡易的無線轉有線橋接器!

2015/4/18

【樹莓派】無線網路轉送有線網路橋接

參考下圖,需求是讓RPi連接無線的網路訊號並用網路線連接到一台電腦,讓電腦能夠透過樹莓派來上網。
無線網路的設定可以參考【設定無線網路】,這篇文章著重在有線的網路封包轉傳。在Linux上要實現封包轉送只需要設定好iptable就可以了!

首先編/etc/sysctl.conf檔案,將net.ipv4.ip_forward=1這行註解拿掉
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
重新載入設定檔,讓設定生效
sysctl --system

完成上述動作RPi的iptable功能就被啟動了,接著設定RPi的eth0網卡IP設定為192.168.0.1
,修改/etc/network/interfaces
#iface eth0 inet dhcp
allow-hotplug eth0
iface eth0 inet static
  address 192.168.0.1
  network 192.168.0.0
  netmask 255.255.255.0
  broadcast 192.168.0.255
  gateway 192.168.0.1
修改後重新啟動eth0
ifconfig eth0 down
ifconfig eth0 up

最後設定iptable,在家目錄下編輯一個新檔nat.sh
#!/bin/sh
IPT=/sbin/iptables
LOCAL_IFACE=eth0
INET_IFACE=wlan0
INET_ADDRESS=`ifconfig wlan0 | grep "inet addr" | cut -d ' ' -f 12 | cut -d : -f 2`

# Flush the tables
$IPT -F INPUT
$IPT -F OUTPUT
$IPT -F FORWARD

$IPT -t nat -P PREROUTING ACCEPT
$IPT -t nat -P POSTROUTING ACCEPT
$IPT -t nat -P OUTPUT ACCEPT

# Allow forwarding packets:
$IPT -A FORWARD -p ALL -i $LOCAL_IFACE -j ACCEPT
$IPT -A FORWARD -i $INET_IFACE -m state --state ESTABLISHED,RELATED -j ACCEPT

# Packet masquerading
$IPT -t nat -A POSTROUTING -o $INET_IFACE -j SNAT --to-source $INET_ADDRESS

執行nat.sh
chmod +x ~/nat.sh
~/nat.sh

到這裡一切就設定完成了,接著在電腦端設定固定IP(ex. 192.168.0.2)、路由與DNS等資訊就可以透過RPi上網了。

【樹莓派】編譯一個Hello Kernel Module(Driver)

  編譯Driver給RPi使用需要準備Toolchain與Kernel Source Tree,步驟可以參考【重編Kernel,客制化自己的核心】,以下範例目錄結構為
 /home/acos
--+ rpi
----+ linux: 存放Kernel Source
----+ tools: 存放Toolchain
----+ driver: 存放此範例檔案hello.c與Makefile

hello.c
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
 printk(KERN_ALERT "Hello, RPi!\n");

 return 0;
}

static void hello_exit(void)
{
 printk(KERN_ALERT "Goodbye, RPi\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile
obj-m := hello.o

all:
 make -C /home/acos/rpi/linux M=$(PWD) modules

clean:
 make -C /home/acos/rpi/linux M=$(PWD) clean

進到driver目錄下執行
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
結束後會在目錄中看到hello.ko,將這檔案複製到RPi上進行載入與卸除動作就會看到對應訊息
root@raspberrypi:~# insmod hello.ko
[ 5835.187137] Hello, RPi!
root@raspberrypi:~# rmmod hello
[ 5842.134762] Goodbye, RPi

2015/4/17

【樹莓派】開機自動登入

要讓RPi自動登入並執行預設程式很簡單,只需要修改/etc/inittab檔案與登入帳號家目錄下的.profile檔案即可。

首先設定自動登入,編輯/etc/inittab檔案:
sudo vi /etc/inittab
把最後一行敘述T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100註解起來,並加入T0:23:respawn:/bin/login -f pi ttyAMA0 </dev/ttyAMA0 >/dev/ttyAMA0 2>&1
#Spawn a getty on Raspberry Pi serial line
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
T0:23:respawn:/bin/login -f pi ttyAMA0 </dev/ttyAMA0 >/dev/ttyAMA0 2>&1
存檔重開機,就會自動使用pi帳號完成登入,若想要以root身分自動登入,則將上面敘述的pi改為root即可。

自動登入後若想自動執行程式,可以從~/.profile帶起。

2015/4/15

【樹莓派】重編Kernel,客制化自己的核心

編譯Linux Kernel,準備好Toolchain(即Compiler, Linker等工具)與Kernel Source就可以開始了!此範例採用Cross-Compile,也就是在一般X86架構的PC上為樹莓派編譯核心,編譯完成在將相關檔案複製到SD Card上讓樹莓派使用。

過程中會使用到git抓取toolchain與kernel source,因此須先安裝git套件:
sudo apt-get install git-core

接著在家目錄建立rpi目錄並使用git下載toolchain與kernel source
cd ~
mkdir rpi
cd rpi
git clone https://github.com/raspberrypi/tools
git clone --depth=1 https://github.com/raspberrypi/linux
完成上述動作後可在rpi目錄中看到tools與linux兩個子目錄

為了方便後續操作,將tools套件所在目錄加到$PATH變數
vi ~/.bashrc
在檔案尾端加入敘述
PATH=$PATH:/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin
如果使用的作業系統平台是64位元的話,需換成下面這敘述
PATH=$PATH:/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
修改完成後重新關聯~/.bashrc讓設定生效
source ~/.bashrc

完成工具與環境設定後,就可以開始進行核心編譯了
進入kernel source目錄,設定預設值,進行編譯!
cd ~/rpi/linux
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcmrpi_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
如果使用的是多核心CPU可以在make指令中使用-j <n>參數讓編譯動作分散在各核心中加快整體速度,其中<n>是核心數量,過一段時間完成後若沒出現錯誤就已經完成核心編譯了。

最後將SD卡插入電腦,把剛編譯好的模組檔案與核心檔案取代原先檔案後即完成所有步驟
mkdir /mnt/fat32
mkdir /mnt/ext4
sudo mount /dev/sdb1 /mnt/fat32
sudo mount /dev/sdb2 /mnt/ext4
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/mnt/ext4 modules
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/mnt/ext4 modules_install
sudo cp /mnt/fat32/kernel.img /mnt/fat32/kernel-backup.img
sudo cp arch/arm/boot/Image /mnt/fat32/kernel.img
sudo umount /mnt/fat32
sudo umount /mnt/ext4

將SD卡插入樹莓派,開機登入後查看歡迎訊息顯示的核心資訊,可以驗證一下核心產生的時間是不是正確的!

2015/4/9

【樹莓派】查看版本、規格與製造商

在第一代不同的Model在GPIO腳位的設計上有些許的不同,板子上的記憶體大小也不一樣,要知道自己手上的RPi是屬於哪個版本、製造商是誰可以透過查看CPU資訊來得知
cat /proc/cpuinfo
顯示結果會有像下面的訊息
Hardware        : BCM2708
Revision        : 000e
Serial          : 00000000bf257857
依據Revision再去查表即可知道詳細的相關資訊
像手上這塊板子是000e,則可以知道是Model B 2.0、記憶體是512MB,由Sony製造。
表格資訊會隨時間持續增加項目,最新資料可到elinux.org查詢。

2015/3/30

【樹莓派】編譯一個Hello World程式在RPi上執行

要在PC上編譯一個可以在Raspberry Pi上執行的程式必須透過Cross-Compile才能達成。Cross-Compile的意思就是在A架構電腦(比如你用的一般電腦)上編譯B架構電腦(比如樹莓派)能執行的二進位程式,為了讓電腦能夠Cross-Compile出樹莓派能接受的執行檔,須要先在電腦上安裝Cross-Compiler:
1
sudo apt-get install gcc-arm-linux-gnueabi
這裡寫了一個簡單的Hello World程式做為示範
1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Hello Raspberry Pi!\n");
}
透過剛安裝的Cross-Compiler進行編譯:
1
arm-linux-gnueabi-gcc -o hello hello.c
產生的執行檔可以使用file指令查看執行檔格式是否為ARM架構
若正確則可將檔案放到樹莓派上執行,應該可以得到下列結果:

2015/3/18

解決呼叫某些系統函數發生'無法解析的外部符號'問題的兩種方法

Windows有些系統函數被存放在像是Shlwapi.lib函數庫中而非常用到的kernel32.lib,在Visual Studio編譯環境中,Linker預設是不會去找Shlwapi.lib等較不常用的Library進行鏈結,所以當呼叫到如PathFileExists等系統函數,編譯過程中就會收到類似錯誤:
1>Source.obj : error LNK2001: 無法解析的外部符號 __imp__PathFileExistsA@4
1>E:\Tmp\ConsoleApplication13\Release\ConsoleApplication13.exe : fatal error LNK1120: 1 個無法解析的外部符號
1>
1>建置失敗。

解決方法有兩種:
其一是透過設定專案屬性→連結器→輸入→其他相依性,加入*.lib(如Shlwapi.lib)
其二是透過假指令#pragma告知編譯環境將調用到*.lib
#pragma comment(lib, "Shlwapi.lib")

2015/2/7

【C#】客製化外觀的三態按鈕(Three State CheckBox)

.NET提供的CheckBox元件本身就支援三態按鈕(三種選取狀態),只要把屬性ThreeState設定為True即可
但有時候預設的外觀顯示與操作體驗可能無法準確呈現使用情境,這時可以新建一個類別繼承CheckBox並重載OnPaint與OnClick方法來客製化符合使用情境的三態按鈕
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class CheckBoxTriState : CheckBox
{
    public CheckBoxTriState()
    {
        ThreeState = true;
        CheckState = System.Windows.Forms.CheckState.Indeterminate;
        Text = String.Empty;
        Size = new Size(20, 40);
    }

    protected override void OnPaint(PaintEventArgs pevent)
    {
        Graphics g = pevent.Graphics;
        g.Clear(Color.LightGray);
        Brush b = new SolidBrush(Color.DarkGray);
        g.DrawRectangle(new Pen(b), 0, 0, Width, Height);

        float radius = (float)(Width * 0.9);
        float margin = (float)(Width * 0.05);
        if (CheckState == System.Windows.Forms.CheckState.Indeterminate)
            g.FillEllipse(b, margin, (float)(Height / 4.0) + margin, radius, radius);
        else if (CheckState == System.Windows.Forms.CheckState.Checked)
            g.FillEllipse(b, margin, margin, radius, radius);
        else
            g.FillEllipse(b, margin, (float)(Height / 2.0 - margin), radius, radius);
    }

    protected override void OnClick(EventArgs e)
    {
        Point pos = this.PointToClient(Cursor.Position);
        if (pos.Y < Height / 3)
            CheckState = System.Windows.Forms.CheckState.Checked;
        else if (pos.Y > Height * 2 / 3)
            CheckState = System.Windows.Forms.CheckState.Unchecked;
        else
            CheckState = System.Windows.Forms.CheckState.Indeterminate;
    }
}
使用方始與一般的CheckBox相同
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        CheckBoxTriState check = new CheckBoxTriState();
        check.Location = new Point(10, 10);
        Controls.Add(check);

        CheckBoxTriState check2 = new CheckBoxTriState();
        check2.Location = new Point(40, 10);
        check2.CheckState = CheckState.Checked;
        Controls.Add(check2);

        CheckBoxTriState check3 = new CheckBoxTriState();
        check3.Location = new Point(70, 10);
        check3.CheckState = CheckState.Unchecked;
        Controls.Add(check3);
    }
}
Keyword:How to customize a tri-state checkbox / button in C#

2015/2/2

【C#】客制化可調大小的勾選元件(CheckBox)

CheckBox的方框大小是以Hard Coded方式寫死的,因此沒辦法藉由設定CheckBox任何參數來改變勾選框框的大小,但可以透過override方是依自己需求重新繪製方框,直接看程式碼:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class CheckBoxEx : CheckBox
{
    public CheckBoxEx()
    {
    }

    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            base.Text = value;
            Size size = TextRenderer.MeasureText(value, Font);
            if (Width < size.Width + ClientSize.Height)
                Width = size.Width + ClientSize.Height;
        }
    }

    public override Font Font
    {
        get
        {
            return base.Font;
        }
        set
        {
            base.Font = value;
            Size size = TextRenderer.MeasureText(Text, value);
            if (Width < size.Width + ClientSize.Height)
                Width = size.Width + ClientSize.Height;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        int h = ClientSize.Height;
        Rectangle rc = new Rectangle(new Point(0, 0), new Size(h, h));
        e.Graphics.Clear(Parent.BackColor);
        ControlPaint.DrawCheckBox(e.Graphics, rc,
            this.Checked ? ButtonState.Checked : ButtonState.Normal);
        SizeF size = e.Graphics.MeasureString(Text, Font);
        e.Graphics.DrawString(Text, this.Font,
            new SolidBrush(Color.Blue), new PointF(h, size.Height < h ? (h - size.Height) / 2 : 0));
    }
}
重點在於重載OnPaint這個繪圖函數,在函數內使用ControlPaint.DrawCheckBox重新繪製方框外形,因重載了原本OnPaint,因此要自己把文字補畫上去(參考44 ~ 46行)。
  因重設字型大小或顯示文字內容會影響CheckBox的呈現寬度,因此一併重載TextFont屬性,當使用者更改這兩個屬性值,則要重新計算CheckBox的寬度,否則文字呈現可能會被截斷。
  使用方法跟一般CheckBox一樣:
1
2
3
4
5
6
CheckBoxEx check = new CheckBoxEx();
check.Location = new Point(40, 40);
check.ClientSize = new Size(30, 30);
check.Text = "Hello CheckBox";
check.Font = new System.Drawing.Font("新細明體", 18);
Controls.Add(check);

Keyword:Customize checkbox size