วันอังคารที่ 17 พฤศจิกายน พ.ศ. 2558

ตอนที่ 8 ตัวอย่างการเขียนโปรแกรมรับ-ส่งข้อมูลผ่านทาง SPI

ตอนที่ 8 ตัวอย่างการเขียนโปรแกรมรับ-ส่งข้อมูลผ่านทาง SPI
         0145
         SPI (Serial Peripheral Interface) เป็น การเชื่อมต่อสื่อสารแบบอนุกรมโดยอาศัยสัญญาณนาฬิกาเป็นตัวกำหนดจังหวะการรับ ส่งข้อมูล (Synchronous) ที่สามารถส่งข้อมูลไปยังปลายทางและรับข้อมูลจากปลายทางกลับมาในครั้งเดียว กัน (Full Duplex)
         SPI แบ่งอุปกรณ์ออกเป็น 2 ฝั่ง คือ Master เป็นตัวควบคุมการรับส่งข้อมูลโดยในที่นี้คือไมโครคอนโทรลเลอร์ กับ Slave เป็นอุปกรณ์ที่รอรับคำสั่งจาก Master โดย Slave มีได้มากกว่า 1 ตัว
SPI ใช้สายสัญญาณทั้งหมด 4 เส้นดังนี้
         1. MOSI (Master Out Slave In)      Master -> Slave           Shared
         2. MISO (Master In Slave Out)      Slave -> Master           Shared
         3. SCLK (Clock)                           Master -> Slave           Shared
         4. CS (Chip Select)                      Master -> Slave           Not Shared

0131
            Master สามารถเชื่อมต่อกับ Slave ได้มากกว่า 1 ตัว โดยทุกตัวจะใช้ขา MOSI MISO และ SCLK ร่วมกัน แล้ว Master จะส่งสัญญาณที่ขา CS เพื่อเลือกว่าในขณะนั้น Master ติดต่อกับ Slave ตัวใด
รูปแบบสัญญาณใน SPI BUS
รูปแบบสัญญาณ SPI มี 4 รูปแบบ แตกต่างกันที่ขอบสัญญาณนาฬิกา (Clock Polarity) และเฟส (Phase)
0132
          - เมื่อ CPHA=0 และ CPOL=0 สัญญาณนาฬิกา (Clock) ในสถานะปกติจะเป็น Low และจะรับ-ส่งข้อมูลที่ขอบขาขึ้นของสัญญาณนาฬิกา (Rising Edge Clock)
          - เมื่อ CPHA=0 และ CPOL=1 สัญญาณนาฬิกา (Clock) ในสถานะปกติจะเป็น High และจะรับ-ส่งข้อมูลที่ขอบขาลงของสัญญาณนาฬิกา (Falling Edge Clock)
          - เมื่อ CPHA=1 และ CPOL=0 สัญญาณนาฬิกา (Clock) ในสถานะปกติจะเป็น Low และจะรับ-ส่งข้อมูลที่ขอบขาลงของสัญญาณนาฬิกา (Falling Edge Clock)
          - เมื่อ CPHA=1 และ CPOL=1 สัญญาณนาฬิกา (Clock) ในสถานะปกติจะเป็น High และจะรับ-ส่งข้อมูลที่ขอบขาขึ้นของสัญญาณนาฬิกา (Rising Edge Clock)
ดังนั้น จึงกำหนดเป็น Mode การทำงานได้ 4 โหมด คือ
          o Mode 0 = CPOL=0 และ CPHA=0
          o Mode 1 = CPOL=0 และ CPHA=1
          o Mode 2 = CPOL=1 และ CPHA=0
          o Mode 3 = CPOL=1 และ CPHA=1
ทดสอบใช้งาน SPI Driver บนบอร์ด Raspberry Pi
          ในการทดสอบนี้เป็นการทดสอบรับ-ส่งข้อมูลผ่าน SPI โดย Loopback ส่งข้อมูลออกจากขา MOSI และรับข้อมูลเข้ามาทางขา MISO
- เปิดโปรแกรม LXTerminal
- ดาวน์โหลดซอร์สโค้ดโปรแกรมทดสอบ Loopback พิมพ์คำสั่ง
wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-3.10.y /Documentation /spi/spidev_test.c
0134
- คอมไพล์โปรแกรม พิมพ์คำสั่ง gcc -o spidev_test spidev_test.c
0135
- ทดสอบ Run โปรแกรม พิมพ์คำสั่ง ./spidev_test -D /dev/spidev0.0
0136
            โปรแกรมจะแสดงรายละเอียดที่ตั้งค่า SPI และแสดงข้อมูลที่ได้รับจากขา MOSI จากรูปพบว่า Data ทั้งหมดเป็น 0 ทั้งหมดหรืออาจเป็น FF ทั้งหมดเนื่องจากขาของ MOSI ยังไม่ได้ต่อกับอุปกรณ์ใดๆ
- ต่อสัญญาณขา MOSI เข้ากับขา MISO
0137
- ทดสอบ Run โปรแกรมใหม่อีกครั้ง พิมพ์คำสั่ง ./spidev_test -D /dev/spidev0.0
0138
จะมีข้อมูลที่อ่านได้จากขา MOSI ซึ่งตรงกับที่กำหนดไว้ในโค้ดโปรแกรม
0139 1
- ในโปรแกรมตัวอย่างนี้ สามารถทดสอบกำหนดตั้งค่าต่างๆให้กับ SPI ได้โดยกำหนดพารามิเตอร์ซึ่งสามารถดูเมนูวิธีการตั้งค่าโดย พิมพ์คำสั่ง ./spidev_test -x (โดย x คือ ตัวอักษรอะไรก็ได้ที่ไม่มีอยู่ในลิสต์คำสั่ง)
0139
- ทดสอบงาน SPI0 กำหนด Max Speed เป็น 1 MHz เปลี่ยน Mode SPI เป็นโหมด 3 พิมพ์คำสั่ง ./spidev_test -D /dev/spidev0.0 –s 1000000 – H –O
0140
ทดลองใช้งาน SPI กับไลบรารี่ WiringPi บน Qt
            ในการทดลองนี้เป็นการนำโมดูล ADXL345 Digital Accelerometer (ESEN067 Triple Axis Accelerometer Breakout - ADXL345) สำหรับวัดค่าความเร่งมาทดลองเขียนโปรแกรมผ่าน SPI ไปสั่งงานและอ่านค่าความเร่งจาก ADXL345
            ก่อนทำการทดลองมาทำความรู้จัก ADXL345 สักเล็กน้อยเพื่อให้เข้าใจความหมายของข้อมูลที่รับส่งกับตัวโมดูล โดยเราสามารถติดต่อสื่อสารกับ ADXL345 ได้ 2 รูปแบบ คือ SPI กับ I2C แต่ในการทดลองนี้เลือกใช้แบบ SPI โดยในคู่มือของ ADXL345 ระบุว่าสามารถใช้สัญญาณนาฬิกาเพื่อรับ-ส่งข้อมูลแบบ SPI ได้สูงสุด 5 MHz และใช้ CPOL =1 และ CPHA = 1 คือ Mode 3 นั่นเอง
รูปแบบ Protocol ที่ใช้ใน ADXL 345
            เราสามารถสั่งงาน ADXL345 ได้ โดยการระบุตำแหน่งของรีจิสเตอร์ภายในที่เราต้องการเข้าไปเขียนหรืออ่าน ข้อมูลออกมาโดยส่งไปทางขา MOSI โดยข้อมูลไบต์แรกใช้เพื่อระบุว่าเป็นการอ่านหรือเขียนและตำแหน่งที่ต้องการ เข้าถึง หากเป็นการเขียนข้อมูลลงไปในตำแหน่งนั้นไบต์ต่อไปที่ส่งจะเป็นข้อมูลที่ต้อง การเขียน แต่หากเป็นการอ่านข้อมูล ไบต์ของข้อมูลจะถูกส่งออกจากทางขา MISO
0141
ความหมายของข้อมูลไบต์แรกที่ถูกส่งออกไปเพื่อควบคุมการอ่านหรือเขียนและกำหนดตำแหน่ง
0142
           - บิตที่ 0 ถึงบิตที่ 5 (A0 - A5) คือ บิตที่ใช้ระบุตำแหน่งรีจิสเตอร์ของ ADXL345 ซึ่งมีขนาด 5 บิต ตั้งแต่ตำแหน่งที่ 0x00 จนถึง 0x39
           - บิตที่ 6 คือ MB (Multiple Byte) ให้กำหนดเป็น 1 (High) เมื่อต้องการรับ-ส่งข้อมูลหลายๆ ไบต์ในการรับ-ส่งครั้งเดียว
           - บิตที่ 7 (MSB) คือ R/W (Read/Write) หากกำหนดเป็น 0 หมายถึงต้องการเขียนข้อมูลลงไปในตำแหน่งที่ระบุไว้ใน A0 – A5 แต่หากกำหนดเป็น 1 หมายถึงต้องการอ่านข้อมูลจากตำแหน่งนั้น
รีจิสเตอร์ที่ใช้งานในการทดลองนี้
- DATA_FORMAT (Address 0x31)
0143
             ในการทดลองนี้กำหนดค่าให้ DATA_FORMAT = 0x01 (ให้ D0 เป็น1) คือ ให้ ADXL345 อ่านค่าที่ + 4g
- POWER_CTL (Address 0x2D)
0144
             ในการทดลองนี้กำหนดค่าให้ Address POWER_CTL = 0x08 (ให้ D3 เป็น 1) คือ ให้ทำงานในโหมดการวัดค่า (Measure)
- DATAx0 DATAx1 DATAy0 DATAy1 DATAz0 DATAz1 (Address 0x32 - 0x37)
             ตำแหน่ง 0x32 – 0x37 เป็นตำแหน่งที่เก็บค่าความเร่งที่วัดได้แต่ละค่าตามลำดับ
ทดลองเขียนโปรแกรมอ่านค่า ADXL345 ผ่าน SPI
- ต่อโมดูล ADXL345 เข้ากับบอร์ด Raspberry Pi ดังภาพ
0145- เปิดโปรแกรม LXTerminal และเปิดโปรแกรม Qt Creator พิมพ์คำสั่ง sudo qtcreator
- สร้าง Project ใหม่ชื่อ adxl_345
- เปิดไฟล์ adxl_345.pro แล้วเพิ่มโค้ดลงไปดังนี้
LIBS += -L/usr/local/lib -lwiringPi -lwiringPiDev
INCLUDEPATH += /usr/local/include
0146
- ออกแบบหน้าตาโปรแกรมโดยมี Widget ดังนี้
         o Pushbutton 3 อัน โดยแสดงข้อความบนปุ่มดังนี้
                “INIT SPI”
                “Start”
                “Stop”
         คลิกขวาที่ PushButton ทีละอัน แล้วสร้าง Slot เลือก Signal = clicked ให้ครบทั้ง 3 อัน
0147
         o Text Edit
         หน้าตาโปรแกรมที่ออกแบบควรจะมีรูปแบบประมาณนี้
0148- ไปที่ไฟล์ mainwindows.cpp แล้วเพิ่มไลบรารี่ ประกาศตัวแปร และกำหนดค่าต่างๆ ดังนี้
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <QTimer>
#include <sys/ioctrl.h>
#include <linux/spi/spidev.h>
int fd;
Qtimer *timer;
#define DEVID 0x00
#define POWER_CTRL 0x2D
#define DATA_FORMAT 0x31
#define DATA_X 0x32
#define DATA_Y 0x34
#define DATA_Z 0x36
#define CMD_READ 0x80
#define CMD_MB 0x40
#define CMD_WRITE 0x00
0149
- เพิ่มโค้ดลงในฟังก์ชั่น void MainWindow::on_pushButton_clicked() ดังนี้
0150
             บรรทัดที่ 36 เปิดใช้งาน SPI ช่อง 0 และกำหนดความเร็วสัญญาณนาฬิกาที่ 4 MHz
             บรรทัดที่ 38 หากเปิดใช้งานไม่สำเร็จให้แสดงข้อความใน Text Edit
             บรรทัดที่ 44 เนื่องจากไลบรารี่ WiringPi ไม่ได้ทำฟังก์ชั่นให้เข้าไปปรับ SPI Mode ได้ เราจึงต้องใช้ฟังก์ชั่น ioctrl เข้าไปปรับให้ใช้ SPI ใน Mode 3 ได้
             บรรทัดที่ 45 หากตั้งค่า SPI Mode ไม่สำเร็จให้แสดงข้อความใน Text Edit
             บรรทัดที่ 51 ฟังก์ชั่น ioctrl อ่านค่า Speed Clock ที่ตั้งเอาไว้มาแสดงใน Text Edit
             บรรทัดที่ 53 ฟังก์ชั่น ioctrl อ่านค่า SPI Mode ที่ตั้งเอาไว้มาแสดงใน Text Edit
             บรรทัดที่ 55 สืบทอดคลาส Qtimer
             บรรทัดที่ 56 สร้าง Slot และ Signal ให้กับ Timer
- สร้างฟังก์ชั่น void write_register(unsigned char reg,unsigned char data) ดังนี้
0151
             บรรทัดที่ 63 ประกาศตัวแปร Array unsigned char เพื่อเก็บข้อมูลที่ต้องการส่งให้ ADXL345
             บรรทัดที่ 65 นำตำแหน่งของรีจิสเตอร์ที่ต้องการเขียนไปเก็บใน data_out ไบต์ที่ 0 และเนื่องจากคำสั่ง Write ในบิตที่ 7 เป็น 0 อยู่แล้วจึงไม่ต้องกำหนด
             บรรทัดที่ 66 นำข้อมูลที่ต้องการเขียนลงรีจิสเตอร์มาเก็บใน data_out ไบต์ที่ 1
             บรรทัดที่ 67 ส่งข้อมูลออกไปทาง SPI ด้วยฟังก์ชั่น wiringPiSPIDataRW(int channel , unsigned char *data , int len)
                    o int channel คือ ช่อง SPI ที่ใช้งาน
                    o unsigned char *data คือ pointer ของข้อมูลที่ต้องการรับ-ส่ง
                    o int len คือ จำนวนไบต์ทั้งหมดที่รับ-ส่ง
- สร้างฟังก์ชั่น QByteArray read_register(unsigned char reg , int len) ดังนี้
0152
             บรรทัดที่ 73 ประกาศตัวแปร QByteArray dat; เพื่อเก็บข้อมูลสำหรับส่งออกไปเมื่อทำงานจบฟังก์ชั่น
             บรรทัดที่ 74 ประกาศตัวแปร Array unsigned char เพื่อเก็บข้อมูลที่ต้องการส่งให้ ADXL345
             บรรทัดที่ 75 ตรวจเช็คว่าจำนวนไบต์ของข้อมูลที่ต้องการอ่านกลับมาว่ามีค่ามากกว่า 1 ไบต์หรือไม่
             บรรทัดที่ 76 หากจำนวนข้อมูลที่ต้องการอ่านมากกว่า 1 ไบต์ให้เก็บค่าตำแหน่งของรีจิสเตอร์ลงในไบต์ที่ 0 ของ data_out กำหนดบิตที่ 7 เป็น 1 (อ่านข้อมูลจากรีจิสเตอร์) และบิตที่ 6 เป็น 1 (อ่านแบบ Multiple Byte) จาก reg|CMD_READ|CMD_MB หรือ 0xXX|0x80|0x40 ทำให้ data_out[0] มีค่าเท่ากับ 0b11xxxxxx
             บรรทัดที่ 78 หากอ่านค่ากลับมาแค่ 1 ไบต์ให้เก็บค่าตำแหน่งของรีจิสเตอร์ลงในไบต์ที่ 0 ของ data_out กำหนดบิตที่ 7 เป็น 1 (อ่านข้อมูลจากรีจิสเตอร์)
             บรรทัดที่ 80 ส่งข้อมูลใน data_out ออกไปทาง SPI ด้วยฟังก์ชั่น wiringPiSPIDataRW ซึ่ง ADXL345 จะส่งข้อมูลจากรีจิสเตอร์ที่เราต้องการอ่านค่ากลับมา โดยฟังก์ชั่น wiringPiSPIDataRW จะเขียนข้อมูลที่ได้รับจาก ADXL345 มาเก็บในตัวแปร data_out ต่อจากเดิมที่เราใช้ส่งคำสั่งอ่านข้อมูลออกไป ในที่นี้ เราส่งข้อมูลไปเพียง 1 ไบต์ที่ data_out[0] ดังนั้นข้อมูลที่ ADXL345 ส่งกลับมาจะถูกเก็บตั้งแต่ใน data_out[1] เป็นต้นไป
             บรรทัดที่ 81 นำ Data จาก data_out ไปเก็บในตัวแปร QByteArray dat โดยไม่เก็บ data_out[0] ลงไปใน dat ด้วย
             บรรทัดที่ 85 ส่งค่า return ตัวแปร dat ออกไป
- ไปที่ไฟล์ mainwindows.h
- ประกาศชื่อฟังก์ชั่น interval() ภายใต้ private slot และฟังก์ชั่น write_register กับ read_register ภายใต้ private
0153
- ไปที่ไฟล์ mainwindows.cpp เพิ่มโค้ดลงในฟังก์ชั่น
void MainWindow::on_pushButton_2_clicked() ดังนี้
0154
             บรรทัดที่ 92 เขียนค่า 0x01 ลงในรีจิสเตอร์ DATA_FORMAT เพื่อกำหนดให้ ADXL345อ่านค่าที่ + 4g
             บรรทัดที่ 92 อ่านค่ารีจิสเตอร์ DATA_FORMAT ออกมาแสดงใน Text Edit
             บรรทัดที่ 95 เขียนค่า 0x08 ลงไปในรีจิสเตอร์ POWER_CTL เพื่อกำหนดให้ADXL ทำงานในโหมดการวัด (Measure)
             บรรทัดที่ 96 อ่านค่ารีจิสเตอร์ POWER_CTL ออกมาแสดงใน Text Edit
             บรรทัดที่ 99 สั่งให้ Timer ทำงาน มี interval ทุก 200 ms
- เพิ่มโค้ดลงในฟังก์ชั่น void MainWindow::on_pushButton_3_clicked() ดังนี้
0155
             บรรทัดที่ 104 สั่งให้ Timer หยุดทำงาน
- สร้างฟังก์ชั่น interval() ดังนี้
0156
             บรรทัดที่ 110 เคลียร์ข้อความใน Text Edit
             บรรทัดที่ 111 อ่านข้อมูลจากรีจิสเตอร์ตั้งแต่ 0x32 (DATAx0) จำนวน 6 ไบต์หมายถึง การอ่านค่า ADXL345 จาก DATAx0 จนถึง DATAz1 ในครั้งเดียว
             บรรทัดที่ 112 ประกาศตัวแปร int ขนาด 16 บิต มาเก็บค่าที่อ่านได้จาก ADXL345
             บรรทัดที่ 114 เนื่องจากค่าความเร่งที่อ่านได้ในแต่ละแกนมีขนาด 10 บิต แยกเก็บในตัวแปรขนาด 8 บิต 2 ตัว ก่อนนำไปใช้งานจึงต้องนำข้อมูล 8 บิตทั้ง 2 อันมาเก็บรวมกันก่อนโดย Datax0 จะเป็น LSB และ Datax1 เป็น MSB และทำเช่นเดียวกันกับ Data ของทุกแกน
             บรรทัดที่ 118 สร้างตัวแปรแบบ float มาเก็บค่าที่ได้จากทั้ง 3 แกนเมื่อคำนวณเป็นแรง g
             บรรทัดที่ 120 ในตอนแรกกำหนดให้รีจิสเตอร์ DATA_FORMAT = 0x01 ทำให้ ADXL345 อ่านค่าที่ +4g และ ADXL345 มีค่าความละเอียด 10 บิท คือ ตั้งแต่ 0 ถึง 1023 ดังนั้นที่ -4g ค่าที่อ่านได้จะเท่ากับ 0 และที่ +4g ค่าที่อ่านได้เท่ากับ 1023 ดังนั้น ถ้าอ่านค่าได้ 1 จะมีค่าเท่ากับ 8/1023 = 0.0078g หากนำค่าในแต่ละแกนที่ ADXL345 อ่านได้มาคูณกับ 0.0078 ก็จะได้ค่าแรง g ที่กระทำต่อแกนนั้นๆ
             บรรทัดที่ 124 นำค่า g ที่คำนวณได้มาแสดงใน Text Edit
- ทดลอง Run โปรแกรม
0157ทดสอบวาง ADXL345 ในทิศทางต่าง
0158ทดลองนำค่า g ที่วัดได้ในแกน x y z มาคำนวณเป็นค่ามุม Roll / Pitch
- เพิ่ม #include <qmath.h> ใน mainwindows.cpp
0160
- เพิ่มโค้ดลงไปในฟังก์ชั่น interval() ดังนี้
0161
- ทดลอง Run โปรแกรม

ตอนที่ 7 ตัวอย่างการเขียนโปรแกรมรับ-ส่งข้อมูลผ่านทางบัส I2C

ตอนที่ 7 ตัวอย่างการเขียนโปรแกรมรับ-ส่งข้อมูลผ่านทางบัส I2C
       099
        I2C เป็นการสื่อสารแบบอนุกรมแบบ Synchronous ด้วยสายสัญญาณเพียง 2 เส้น คือ สายสัญญาณข้อมูล SDA (Serial Data Line) และสายสัญญาณนาฬิกา SCL (Serial Clock Line) โดย I2C แบ่งการทำงานออกเป็น 4 โหมดตามความเร็วในการรับส่งข้อมูลดังนี้
        1. Normal Mode: 100Kbps
        2. Fast Mode: 400Kbps
        3. Fast Mode Plus: 1Mbps
        4. High Speed Mode: 3.4 Mbps
ตัวอย่างการต่อใช้งาน I2C
094
        จากภาพแสดงให้เห็นว่าในการใช้งาน I2C สามารถต่อใช้งานกับอุปกรณ์ได้มากกว่าหนึ่งตัวบนสายสัญญาณ 1 ชุด (2 เส้น) โดยเลือกติดต่อกับอุปกรณ์ใดได้ด้วยการกำหนดแอดเดรสทางฮาร์ดแวร์ให้กับ อุปกรณ์ตัวนั้นและต้องมีการ Pull-Up ให้กับสายสัญญาณทั้ง 2 เส้น
รูปแบบสถานะในการรับ-ส่งข้อมูล

095
       Start เป็นสถานะที่บอกเริ่มต้นการรับ-ส่งข้อมูล โดยการเปลี่ยนสัญญาณของ SDA จาก High ไปเป็น Low โดยที่ SCL ยังคงเป็น High อยู่
       Control Byte ประกอบไปด้วย 3 ส่วน คือ
       - ID ของอุปกรณ์จำนวน 4 บิต ซึ่งถูกกำหนดมาโดยผู้ผลิต IC หรืออุปกรณ์ I2C
       - Device Address ขนาด 3 บิต สามารถกำหนดได้เองจากการจ่าย Logic หรือต่อขาให้กับ IC
       - Mode ขนาด 1 บิต ใช้กำหนดว่าเป็นการ Read หรือ Write Data กับอุปกรณ์ IC
       ACK หรือ Acknowledge เป็นบิตที่ใช้บอกว่า IC มีการตอบสนองต่อคำสั่งที่ได้รับมาแล้ว
       DATA คือ ข้อมูลที่ต้องการเขียนหรืออ่านออกมาจาก IC ขึ้นกับ Mode ที่เราได้ตั้งค่าเอาไว้
       STOP เป็นสถานะที่บอกให้อุปกรณ์รู้ว่าสิ้นสุดการรับส่งข้อมูลแล้ว โดย SDA จะเปลี่ยนจาก Low เป็น High ในขณะที่ SCL ยังเป็น High อยู่
096
แนะนำโมดูล PCF8591 (EFDV221 PCF8591 Module)
            PCF8591 เป็นชิพที่ใช้สำหรับแปลงสัญญาณ Analog to Digital และ Digital to Analog ทั้ง 2 ทางขนาด 8-bit สั่งงานผ่าน I2C มีรูปแบบการใช้งานที่ไม่ซับซ้อนมากนัก เนื่องจากบนบอร์ด Raspberry Pi ไม่มีขาสัญญาณสำหรับอ่านค่า Analog to Digital โมดูลตัวนี้จึงเหมาะที่จะนำมาทดแทนความสามารถที่ขาดไปนี้ได้ นอกจากนี้บนตัวโมดูลยังมีอุปกรณ์อื่นๆ เช่น Thermister LDR และ Potentiometer ซึ่งให้เอาท์พุตเป็นค่า Analog ทำให้สามารถทดลองอ่านค่าเหล่านี้ได้โดยไม่ต้องไปหาอุปกรณ์เพิ่มเติม
097
ตำแหน่งของ Sensor บน Module
วงจรของโมดูล PCF8591
098
วิธีตั้งค่าจัมพ์เปอร์กับอุปกรณ์บนบอร์ด
099
อุปกรณ์บนบอร์ดสามารถเลือกเชื่อมต่อได้ด้วยการต่อจัมพ์เปอร์ ดังนี้
       - P4 ใช้ต่อ Thermistor เข้ากับช่อง AIN1
       - P5 ใช้ต่อ LDR เข้ากับช่อง AIN0
       - P6 ใช้ต่อ Potentiometer เข้ากับช่อง AIN3
ดังนั้น หากต้องการอ่านค่าจากภายนอกที่ช่อง AIN0 AIN1 และ AIN2 ให้ถอด P4 P5 และ P6 ออก
0100
ค่า Device Address ของโมดูล PCF8591
         จากแผนผังวงจรจะเห็นว่าขา A0 A1 และ A2 ของ PCF8591 ต่อลง GND เอาไว้ทั้ง 3 ขา ทำให้ค่าในบิต Device Address ของชิพ คือ 0 ทั้ง 3 บิต และ Datasheet ของ PCF8591 ได้กำหนด ID มาจากผู้ผลิตคือ 0b1001
0101
          ดังนั้น Slave Address ของโมดูลถูกกำหนดตายตัวไว้ที่ 0b100100 (0x48 ที่ 7 บิต)
                - หากกำหนดสถานะ Write Data ไปยังโมดูล บิตที่ 0 (LSB) จะถูกกำหนดเป็น 0 ทำให้ Slave address ทั้ง 8 บิต มีค่าเป็น                 0b1001000 (0x90)
                - หากกำหนดสถานะ Read Data จากโมดูล บิตที่ 0 (LSB) จะถูกกำหนดเป็น 1 ทำให้ Slave address ทั้ง 8 บิต มีค่าเป็น                   0b1001001 (0x91)
Bus Protocol สำหรับอ่านและเขียนข้อมูลของ PCF 8591
- Bus Protocol สำหรับ Write Data
0102
          o S = เริ่มติดต่อ I2C
          o Address = Slave Address ที่กล่าวถึงในหัวข้อก่อนหน้า สำหรับโมดูลนี้คือ 0x48(7Bits) จากรูปจะเห็นว่าบิตที่ 0 (LSB) ถูกกำหนดให้เป็นลอจิก 0 คือ Write Data เมื่อรวมกับ 0x48 แล้วจึงมีค่า = 0x90 ตามที่อธิบายไปแล้วข้างต้น
          o A = Acknowledge ที่ PCF8591 ตอบรับ Slave Address ที่เราส่งไป
          o Control Byte = ไบต์ที่ใช้ควบคุมการทำงานของ PCF8591 ซึ่งมีรูปแบบการกำหนดดังนี้
0103
              บิตที่ 0 และ 1 สำหรับกำหนดช่องของ A/D ที่ต้องการ
              บิตที่ 2 กำหนด AUTO-INCREMENT FLAG
              บิตที่ 3 กำหนดเป็น 0 เสมอ
              บิตที่ 4 และ 5 สำหรับเลือกรูปแบบ Analog Input
              Bit ที่ 6 สำหรับเปิดใช้งาน Analog Output
              Bit ที่ 7 กำหนดเป็น 0 เสมอ
          o Data Byte คือ ข้อมูลที่เราต้องการส่งไปให้โมดูล
          o P/S หมายถึง Stop หรือ Start
0104
- Bus Protocol สำหรับ Read mode
0105
          o S = เริ่มติดต่อ I2C
          o Address = Slave Address ที่กล่าวถึงในหัวข้อก่อนหน้า สำหรับโมดูลนี้คือ 0x48(7Bits) จากรูปจะเห็นว่าบิตที่ 0 (LSB) ถูกกำหนดให้เป็นลอจิก 1 คือ Read Data เมื่อรวมกับ 0x48 แล้วจึงมีค่า = 0x91 ตามที่อธิบายไปแล้วข้างต้น
          o A = Acknowledge ที่ PCF8591 ตอบรับ Slave Address ที่เราส่งไป
          o Data Byte = ข้อมูลที่โมดูลส่งกลับออกมา
          o P หมายถึง Stop
การต่อ Module PCF8591 เข้ากับ Board Raspberry Pi
0106
ทดสอบ I2C Driver
           ในขั้นตอนนี้เป็นการทดสอบว่าโมดูลหรืออุปกรณ์ที่สื่อสารผ่าน I2C ที่นำมาเชื่อมต่อกับ Raspberry Pi สามารถติดต่อสื่อสารกันได้จริงโดยสามารถลิสต์ Slave Address ของโมดูลที่ต่ออยู่บนบัส I2C มาแสดงได้
- เปิดโปรแกรม LXTerminal
- โหลด Linux Kernel I2C Module พิมพ์คำสั่ง gpio load i2c
0107
           คำสั่ง gpio i2c load สามารถใส่พารามิเตอร์กำหนดความเร็วในการรับ-ส่งข้อมูลเพิ่มเติมได้ เช่น gpio i2c load 1000 หมายถึงกำหนดให้รับ-ส่งด้วยความเร็ว 1000 Kbps
- ลิสต์ Slave address ของโมดูลที่ต่ออยู่บนบัส I2C มาแสดง พิมพ์คำสั่ง gpio i2cd
0108
- จากรูปผลการแสดง Slave address จะเห็นว่ามีโมดูลที่ต่ออยู่บนบัส I2C ที่มี Address = 0x48 นั่นก็คือ Address ของ PCF8591 ตามที่ได้อธิบายไว้ในหัวข้อ ค่า Device Address ของโมดูล PCF8591
*** ก่อนใช้งาน I2C ทุกครั้งต้องแน่ใจว่ามีการโหลด Linux Kernel I2C Module แล้วจึงจะใช้งานได้
ทดลองเขียนโปรแกรมติดต่อกับ Module PCF8591 ผ่าน I2C
- เปิดโปรแกรม LXTerminal แล้วเปิดโปรแกรม Qt Creator พิมพ์คำสั่ง sudo qtcreator
0109
- สร้าง Project ใหม่ชื่อ PCF8951 และเปิดไฟล์ PCF8951.pro
- เพิ่มโค้ดเรียกใช้ไลบรารี่ WiringPi ลงไปดังนี้
LIBS += -L/usr/local/lib -lwiringPi -lwiringPiDev
INCLUDEPATH += /usr/local/include
- ออกแบบหน้าตาโปรแกรมให้มี Widget ดังนี้
           o Pushbutton 2 อัน โดยแสดงข้อความบน Button ดังนี้
                “Start”
                “Stop”
คลิกขวาที่ PushButton ทีละอัน แล้วสร้าง Slot เลือก Signal = clicked ให้ครบทั้ง 2 อัน
0110
           o LCD Number
           o Label
0111ลักษณะหน้าตาโปรแกรมที่ให้ออกแบบ
- ไปที่ไฟล์ mainwindows.cpp เพิ่มไฟล์ include และประกาศตัวแปล
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <QTimer>
int pcf_add = 0x48;
int fd;
Qtimer *timer;
0112
- เพิ่มโค้ดลงในฟังก์ชั่น MainWindow::MainWindow(QWidget *parent):QmainWindow (parent),ui(new Ui::MainWindow) ดังนี้
0113
           บรรทัดที่ 17 เปิดใช้งาน I2C ด้วยฟังก์ชั่น wiringPiI2CSetup (int devId) โดยส่งค่า Slave Address (0x48) ให้กับฟังก์ชั่นแล้ว     ตรวจสอบการเปิดใช้งาน
           บรรทัดที่ 18 หากไม่สามารถเปิดใช้งาน I2C ได้ ให้แสดงข้อความ “Unable to open I2C Device” ที่ label
           บรรทัดที่ 23 หากเปิดใช้งานสำเร็จให้แสดง “Open I2C Device” ใน Text Labelบรรทัดที่ 24 สืบทอดคลาส Qtimer โดยตั้งชื่อว่า timer
           บรรทัดที่ 25 สร้าง Signal กับ Slot ให้ timer โดยกำหนดให้เมื่อครบเวลาตามกำหนด timeout() จะเกิด Signal ให้เข้าไปทำงานใน Slot และสร้างฟังก์ชั่น interval() มารองรับ เมื่อครบเวลาตามกำหนดจะกระโดดเข้ามาทำงานในฟังก์ชั่น read_ADC() ทุกครั้ง
- สร้างฟังก์ชั่น read_ADC() แล้วเขียนโค้ดการทำงานดังนี้
0114
           บรรทัดที่ 38 ประกาศตัวแปร int adc;
           บรรทัดที่ 39 ส่ง Control Byte ออกไปสั่ง PCF8591 โดยในการทดลองนี้ต้องการอ่านค่า Potentiometer บนโมดูลซึ่งต่ออยู่ที่ช่อง   AIN3 กำหนด Control Byte ให้มีค่าเท่ากับ 0x43
0115
                            ซึ่งมีความหมายดังนี้
                                o บิตที่ 0 และ 1 กำหนดให้อ่าน ADC จาก AIN3
                                o บิตที่ 2 ปิด Auto Increment
                                o บิตที่ 3 เป็นลอจิก 0 เสมอ
                                o บิตที่ 4 และ 5 กำหนดโหมดการใช้งาน Analog Input แบบ Four Single-end
                                o บิตที่ 6 เปิดใช้ Analog Output
                                o บิตที่ 7 เป็นลอจิก 0 เสมอ
           บรรทัดที่ 40 อ่านค่า ADC ครั้งแรก (ไม่ได้ใช้งาน)
           บรรทัดที่ 41 อ่านค่า ADC เก็บในตัวแปร int adc;
           บรรทัดที่ 42 นำค่า ADC ที่อ่านได้มาคำนวณกลับเป็นแรงดัน (Voltage)
           บรรทัดที่ 43 นำค่าที่คำนวณเป็นแรงดันแล้วไปแสดงใน LCD Number
- ไปที่ไฟล์ mainwindows.h แล้วเพิ่มชื่อฟังก์ชั่น void read_ADC(); ไว้ภายใต้ private slots:
0116
- กลับมาที่ไฟล์ mainwindows.cpp
- เพิ่มโค้ดลงในฟังก์ชั่น void MainWindow::on_pushButton_clicked() ดังนี้
0117
            บรรทัดที่ 48 สั่งให้ Qtimer ทำงานมี Interval ทุกๆ 10ms
- เพิ่มโค้ดลงในฟังก์ชั่น void MainWindow::on_pushButton_2_clicked() ดังนี้
0118
            บรรทัดที่ 54 ปิด Qtimer
- ก่อนทดลอง Run โปรแกรมเพื่อให้แน่ใจว่าโหลด Linux Kernel I2C Module แล้วให้สั่งโหลด Linux Kernel I2C Module อีกครั้งโดยเปิดโปรแกรม LXTerminal พิมพ์คำสั่ง gpio load i2c
0119
- ทดลอง Run โปรแกรม
         o คลิกที่ปุ่ม Start แล้วหมุน Potentiometer บนโมดูล PCF8591 ดูการเปลี่ยนแปลง
0120 0121
แนะนำการใช้งาน QProcess
           จากการทดลองจะเห็นว่าก่อนการใช้งาน I2C ของ WiringPi เมื่อเปิดบอร์ด Raspberry Pi ขึ้นมาใหม่ทุกครั้งจำเป็นจะต้องโหลด Linux Kernel I2C Module ขึ้นมาก่อนจึงจะใช้งาน I2C ได้ จึงอยากแนะนำคลาส QProcess ใน Qt ซึ่งใช้สำหรับเปิดโปรแกรมและติดต่อสื่อสารกับโปรแกรมภายนอกได้
ตัวอย่างการใช้งาน
           ในตัวอย่างการใช้งาน QProcess นี้จะขออ้างอิงตัวอย่างการใช้งานโมดูล PCF8591 ก่อนหน้าดังนี้
- เพิ่ม #include<Qprocess> และ #include<qdebug.h>
0122
- แก้ไขโค้ดเพิ่มเติมในฟังก์ชั่น MainWindow::MainWindow(QWidget *parent):Qmain Window(parent),ui(new Ui::MainWindow) ดังนี้
0123
           บรรทัดที่ 20 สืบทอดคลาส QProcress
           บรรทัดที่ 21 โหลด Linux Kernel I2C Module ด้วยคำสั่ง gpio load i2c
           บรรทัดที่ 22 รอจนคำสั่งที่เรียก Process ทำงานเสร็จ
           บรรทัดที่ 24 ส่งคำสั่งไปลิสต์ Slave Address ของโมดูลที่ต่ออยู่บนบัส I2C
           บรรทัดที่ 25 รอจนคำสั่งที่เรียก Process ทำงานเสร็จ
           บรรทัดที่ 27 อ่านค่าตอบกลับจากการสั่งงานผ่าน QProcess นำมาเก็บในตัวแปร QByteArray byteArray
           บรรทัดที่ 28 แปลง byteArray ที่เป็น QByteArray มาเก็บในตัวแปร QStringList โดยแบ่งเป็นบรรทัด
           บรรทัดที่ 29 วนรอบอ่านข้อความจาก strline มาเก็บในตัวแปน Qstring line
           บรรทัดที่ 31 แสดงผลข้อความออกทาง qDebug
- ทดลอง Run โปรแกรม ในส่วนเอาท์พุตจะแสดงผลการโหลดและลิสต์ I2C แล้วรันโปรแกรมขึ้นมา
0124
PCF8591 Extention Module (EFDV221 PCF8591 Module)
            WiringPi ได้สร้าง Extention Module เอาไว้แล้วสำหรับ PCF8591 เพื่อเป็น ADC และ DAC ให้กับ Raspberry Pi ช่วยใช้งานได้ง่ายขึ้น มีชุดคำสั่งที่สามารถควบคุม Analog Output และ Analog Input ได้ด้วยชุดคำสั่งที่สั้นและใช้งานง่าย
ตัวอย่างการใช้งาน
- ต่อโมดูล PCF8591 เข้ากับบอร์ด Raspberry Pi
- ถอดจัมพ์เปอร์ P6 ออก
- บนโมดูล PCF8591 ต่อสายที่ AOUT เข้ากับ AIN3
0125
- การทดลองนี้เป็นการสั่งงาน Analog Out ต่อกลับเข้ามาที่ Analog Input เพื่ออ่านค่า Analog
- เปิดโปรแกรม LXTerminal แล้วเปิดโปรแกรม Qt Creator ด้วยคำสั่ง sudo qtcreator
- สร้าง Project ใหม่ชื่อ PCF8591_Ext
- เปิดไฟล์ PCF8591_Ext.pro
- เพิ่มโค้ดเรียกใช้ไลบรารี่ WiringPi ลงไปดังนี้
LIBS += -L/usr/local/lib -lwiringPi -lwiringPiDev
INCLUDEPATH += /usr/local/include
- ออกแบบหน้าตาโปรแกรมโดยมี Widget ดังนี้
      o Pushbutton โดยแสดงข้อความบน Button “Set Output” แล้วคลิกขวาที่ PushButton แล้วสร้าง Slot เลือก Signal = clicked
      o LCD Number
      o Line Edit
- ไปที่ไฟล์ mainwindows.cpp
- เพิ่มโค้ดเรียกใช้ไลบรารี่และประกาศตัวแปลดังนี้
#include <wiringPi.h>;
#include <pcf8591.h>;
#include <QTimer>;
#include <QProcess>;
Qtimer *timer;
int input_base = 100;
int i2caddress = 0x48;
0126
- เพิ่มโค้ดลงในฟังก์ชั่น MainWindow::MainWindow(QWidget *parent) :QmainWindow (parent),ui(new Ui::MainWindow) ดังนี้
0127
             บรรทัดที่ 18 สืบทอดคลาส QProcress
             บรรทัดที่ 19 โหลด Linux Kernel I2C Module ด้วยคำสั่ง gpio load i2c
             บรรทัดที่ 20 รอจน Process คำสั่งเสร็จ
             บรรทัดที่ 21 เริ่มต้นใช้งาน PCF 8591 ด้วยฟังก์ชั่น pcf8591Setup (int pinBase, int i2cAddress)
                              o int pinBase คือค่า pinbase ซึ่งเราสามารถกำหนดเป็นค่าอะไรก็ได้ แต่ต้องเป็นค่าที่มากกว่า 64 (ในที่นี้กำหนด                                      เป็น 100)
                              o int i2cAddress คือค่า Slave address
             บรรทัดที่ 22 สืบทอดคลาส QTimer
             บรรทัดที่ 23 สร้าง Signal กับ Slot ให้ timer โดยกำหนดให้เมื่อครบเวลาตามกำหนด timeout() จะเกิด Signal ให้เข้าไปทำงาน   ใน Slot และสร้างฟังก์ชั่น interval() มารองรับ เมื่อครบเวลาตามกำหนดจะกระโดดเข้ามาทำงานในฟังก์ชั่น read_ADC() ทุกครั้ง
             บรรทัดที่ 24 สั่งให้ Qtimer ทำงาน interval ทุกๆ 10ms
- สร้างฟังก์ชั่น read_ADC() และเขียนโค้ดการทำงานดังนี้
0128
             บรรทัดที่ 33 อ่านค่า Analog ด้วยฟังก์ชั่น analogRead(int pin) โดยค่า Pin จะอ้างอิงจากค่า int pinBase จากฟังก์ชั่น                                             pcf8591Setup ดังนี้
                              o pinBase = AIN0
                              o pinBase+1 = AIN1
                              o pinBase+2 = AIN2
                              o pinBase+3 = AIN3
                              จากวงจรต่อ AOUT เข้ากับ AIN3 จากโค้ดจึงอ้าง pin=Input_base+3 หรือ 100+3
             บรรทัดที่ 34 แปลงค่า Analog ที่รับเข้ามาขนาด 8 บิตให้เป็นแรงดัน
             บรรทัดที่ 35 แสดงค่า Analog ที่แปลงเป็นแรงดันแล้วใน LCD Number
- ไปที่ไฟล์ mainwindow.h
- ประกาศชื่อฟังก์ชั่น void read_ADC() ไว้ภายใต้ private slot:
- ไปที่ไฟล์ mainwindows.cpp
- เพิ่มโค้ดลงในฟังก์ชั่น void MainWindow::on_pushButton_clicked() ดังนี้
0129
            บรรทัดที่ 40 นำค่าจาก Line Edit ที่เป็นตัวแปรชนิด QString มาแปลงเป็นตัวแปร float เป็นค่าแรงดันมา คำนวณย้อนกลับเป็นค่าที่ อยู่ในช่วง 0 ถึง 255
            บรรทัดที่ 41 สั่งให้ PCF8591 จ่ายแรงดันออกไปที่ขา AOUT ด้วยฟังก์ชั่น analogWrite(int pin, int value) โดยค่า Pin อ้างอิง     จาก int pinBase ในฟังก์ชั่น pcf8591Setup เนื่องจาก Analog Output มีเพียงช่องเดียว ดังนั้น ค่า pinbase = input_base คือ 100 value คือค่า 0 ถึง 255 ที่ใช้กำหนด Voltage Output ที่ขา AOUT เป็น ค่าที่อ่านเข้ามาแล้วส่งกลับไป
- ทดลอง Run โปรแกรม
0130