1. Trang chủ
  2. » Luận Văn - Báo Cáo

ARDUINO CHO NGƯỜI MỚI BẮT ĐẦU Quyển rất cơ bản

101 11 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

ARDUINO CHO NGƯỜI MỚI BẮT ĐẦU Quyển rất cơ bản

ARDUINO CHO NGƯỜI MỚI BẮT ĐẦU Quyển minht57 lab Lời nói đầu Arduino cho tảng điện tử phổ biến ưu điểm mã nguồn mở, nhiều tài liệu có cộng đồng phát triển lớn giới nói chung Việt Nam nói riêng Tiếp theo sách “Arduino cho người bắt đầu” (quyển bản) sách thảo luận vấn đề hơn, sâu vào lý thuyết Quyển sách chủ yếu hướng đến bạn muốn tìm hiểu sâu vi điều khiển với khởi đầu Arduino Đương nhiên bàn tán tới thứ khơng thấy khó hiểu, cần phải vượt qua kiến thức mà bạn phải học để hiểu rõ chất vấn đề Quyển sách chia thành phần Phần (Lập trình C) cho bạn kiến thức cần lưu ý ngôn ngữ C lập trình vi điều khiển Phần (Các module ngoại vi) chi tiết vào chương trình (code) module thư viện Arduino để hiểu rõ lập trình vi điều khiển minht57 lab Hướng dẫn đọc sách Bộ sách Arduino dành cho người bắt đầu gồm từ mức độ đến chuyên sâu bao gồm bản, khơng cịn Bộ sách cung cấp cho bạn tư làm việc với vi điều khiển thực hành Arduino đơn Bộ sách cung cấp cho bạn nhiều thông tin mức dạng từ khóa tóm tắt vấn đề (vì giải thích vấn đề rõ ràng, chun sâu lan man dài dịng) Nếu bạn quan tâm vấn đề cụ thể dựa vào từ khóa nêu tìm hiểu thêm internet sách Vì mục tiêu sách hướng đến bạn học lập trình Arduino định hướng chuyên sâu nên sách không tập trung vào module cảm biến, thiết bị chấp hành hay dự án cụ thể Mà cấu trúc sách theo hướng học vi điều khiển tổng quát mà Arduino ví dụ cụ thể Quyển sách sâu vào vấn đề liên quan đến vi điều khiển Quyển sách dành cho bạn có hứng thú tìm hiểu tảng Arduino cách thiết kế chương trình vi điều khiển mà góp phần nên thành cơng tảng Arduino Phần thứ sách lấy kiến thức từ sách "Arduino Software Interals"của tác giả Norman Dunbar Nếu bạn đọc tốt tiếng anh nên tìm đọc sách gốc tác giả để hiểu sâu Các chương sách khơng có liên quan nên bạn đọc đọc chương mà bạn hứng thú Nhưng bạn nên đọc theo thứ tự từ đầu đến cuối chương bạn dễ dàng nắm bắt vấn đề Nếu bạn cảm thấy văn phong cách tiếp cận sách khơng phù hợp với bạn bạn bỏ qua Trong q trình viết biên soạn sách khơng thể tránh khỏi sai sót mặt nội dung hình thức Nhóm tác giả mong nhận góp ý bạn đọc để sách ngày hoàn thiện truyền tải nội dung đến với nhiều bạn đọc Xin cảm ơn bạn đọc minht57 lab Mục lục Lời nói đầu ii Hướng dẫn đọc sách iii Mục lục iv LẬP TRÌNH C 1 LẬP TRÌNH C VÀ NHỮNG ĐIỀU CẦN BIẾT 1.1 Các vùng nhớ chương trình C 1.2 Các biến cần lưu ý Biến volatile Biến register Biến extern 1.3 Các cấu trúc cần biết C Struct Union 1.4 Vùng hoạt động biến 1.5 Giới hạn biến 1.6 Quy trình biên dịch chương trình C Tiền xử lý Biên dịch Biên dịch assembly Liên kết Tải 1.7 Các loại biến chia theo giai đoạn biên dịch 2 4 6 6 10 11 12 13 13 13 14 14 15 CÁC MODULE NGOẠI VI 18 GIỚI THIỆU VỀ ARDUINO 19 General Purpose Input Output (GPIO) 3.1 Giới thiệu 3.2 Hàm pinMode() 3.3 Hàm digitalRead() 3.4 Hàm digitalWrite() 23 23 23 26 27 TIME 4.1 Hàm micros() 4.2 Hàm millis() 4.3 Hàm delay() 4.4 Hàm delayMicroseconds() 28 28 29 30 31 UART 5.1 Giới thiệu 33 33 5.2 5.3 Lý thuyết Lớp HardwareSerial Ngắt (interrupt) Một số hàm thường dùng ANALOG 6.1 Giới thiệu 6.2 Lý thuyết 6.3 Các hàm thường dùng Hàm analogReference Hàm analogRead() Hàm analogWrite 33 35 35 39 46 46 46 47 47 48 49 I2C 7.1 Giới thiệu 7.2 Lý thuyết 7.3 Các hàm thường dùng Hàm TwoWire::begin Hàm TwoWire::end() Hàm TwoWire::beginTransmission() Hàm TwoWire::endTransmission() Hàm TwoWire::write() Hàm TwoWire::requestFrom() Hàm TwoWire::available () Hàm TwoWire::read() Hàm TwoWire::setClock Hàm ngắt ISR(TWI_vect) 52 52 54 58 59 60 61 61 64 65 68 68 69 69 SPI 8.1 Giới thiệu 8.2 Lý thuyết 8.3 Các hàm thường dùng Lớp SPISettings Hàm SPIClass::begin() Hàm SPIClass::end() Hàm beginTransaction() Hàm endTransaction() Các hàm transfer() Hàm SPIClass::usingInterrupt Hàm SPIClass::notUsingInterrupt() 72 72 72 75 75 77 78 79 79 79 81 84 INTERRUPT 9.1 Giới thiệu 9.2 Các hàm thường dùng Hàm interrupt() Hàm noInterrupt() Hàm attachInterrupt() Hàm detachInterrupt() 9.3 Lưu ý làm việc với ngắt 86 86 87 87 87 87 89 89 PHỤ LỤC 91 A MỘT SỐ GHI CHÚ A.1 Cách thiết lập (set/clear) (hoặc nhiều) bit thông qua mặt nạ (mask) 92 92 Lời kết 94 Thơng tin quyền 95 LẬP TRÌNH C LẬP TRÌNH C VÀ NHỮNG ĐIỀU CẦN BIẾT 1.1 Các vùng nhớ chương trình C Cấu trúc nhớ chương trình C chia thành vùng: ◮ Vùng text (code): dùng để chứa mã lệnh biên dịch ◮ ◮ ◮ ◮ chương trình mà bạn viết Các mã lệnh chuyển đến CPU thực Vùng code (code segment) hoạt động dự điều khiển CPU mà bạn can thiệp trực tiếp lúc chương trình chạy Vùng liệu khởi tạo (initialized data segment) vùng chứa biến static, biến toàn cục (global variable) khởi tạo giá trị cụ thể Vùng liệu chưa khởi tạo (uninitialized data segment) vùng chứa biến static, biến toàn cục chưa khởi tạo giá trị giá trị khởi tạo Vùng heap (heap segment) dùng chủ yếu để cấp phát nhớ động (dynamic memory allocation) Điều lưu ý dùng heap khơng tự động giải phóng nhớ ta giải phóng nhớ chương trình kết thúc Nếu khơng giải phóng nhớ gây lãng phí tài ngun làm ảnh hưởng đến việc cấp phát nhớ cho chương trình Vì phải có chế quản lý nhớ phù hợp Điều lưu ý lập trình vi điều khiển KHƠNG NÊN dùng cấp phát nhớ động chương trình Vùng stack (stack segment) nơi cấp phát nhớ cho tham số truyền vào hàm chạy chương trình, biến cục bộ, địa trả sau lần gọi hàm, Cấu trúc nhớ stack dạng LIFO (last in, first out), nghĩa liệu đưa vào cuối lấy 1.1 Các vùng nhớ chương trình C 1.2 Các biến cần lưu ý Biến volatile Biến register Biến extern 1.3 Các cấu trúc cần biết C Struct Union 1.4 Vùng hoạt động biến 10 1.5 Giới hạn biến 11 1.6 Quy trình biên dịch chương trình C 12 Tiền xử lý 13 Biên dịch 13 Biên dịch assembly 13 Liên kết 14 Tải 14 1.7 Các loại biến chia theo giai đoạn biên dịch 15 1: Tiếng anh: Memory layout of C programs Hình 1.1: Phân vùng nhớ chương trình C Nguồn internet Một số lưu ý cần biết: ◮ Các vùng code, data, bss, heap có địa theo từ thấp đến cao, riêng vùng nhớ stack có địa từ cao xuống thấp LẬP TRÌNH C VÀ NHỮNG ĐIỀU CẦN BIẾT ◮ Hiện tượng stack overflow bạn dùng stack vượt ngưỡng giới hạn stack mà bạn có Ví dụ, stack bạn có kích thước 1Mb bạn dùng hết 1Mb mà chương trình tiếp tục chạy, lúc chương trình chạy khơng theo mong muốn, bị crash chương trình Điều nguy hiểm bạn phải tránh trường hợp xảy Ví dụ, bạn biết chương trình điều khiển cánh tay robot, tự nhiên tràn stack bạn khơng cịn kiểm sốt hoạt động cánh tay nữa; điều nguy hiểm thực tế ta quyền kiểm soát Vùng nhớ chứa loại biến nào? Xét chương trình sau: #include int a = 10; int b = 0; int c; static int d = 3; const int e = 8; 10 11 12 13 14 15 16 int sum (int a, int b) { int sum = 0; static crazy_sum = 0; sum = a + b; crazy_sum += sum; return sum; } 17 18 19 int main() { 20 b = a + 10; c = sum(d,e); return 0; 21 22 23 24 } ◮ Biến a biến toàn cục khởi tạo giá trị 10 nên lưu vùng data ◮ Biến b biến toàn cục giá trị khởi tạo nên lưu ◮ ◮ ◮ ◮ ◮ ◮ vùng bss Biến c biến tồn cục khơng khởi tạo giá trị nên lưu vùng bss Biến d biến static khởi tạo nên lưu vùng data Biến e số nên lưu vùng data Tham số a,b đưa vào hàm sum khác với hai biến a,b khởi tạo đầu chương trình Biến sum hàm sum biến cục nên khởi tạo hàm sum gọi biến lưu stack Biến crazy_sum hàm sum biến tĩnh cục khởi tạo với giá trị nên lưu vùng bss Lưu ý, biến khởi tạo lần nên gọi hàm sum lần thứ biến crazy_sum khơng gán lại giá trị LẬP TRÌNH C VÀ NHỮNG ĐIỀU CẦN BIẾT 1.2 Các biến cần lưu ý Biến volatile ◮ Biến volatile dùng để thông báo với compiler giá trị thay đổi không báo trước Đây lỗi thường xảy comiler hoạt động tính tối ưu (optimization) ◮ Biến dùng nhiều lập trình hệ thống nhúng ứng dụng đa luồng (trong viết chủ yếu đề cập đến phần liên quan đến hệ thống nhúng) ◮ Các loại biến thay đổi giá trị đột ngột: • Biến ánh xạ từ ghi ngoại vi đến nhớ (memorymapped peripheral registers) • Biến tồn cục truy xuất từ tiến trình ngắt (interrupt service routine) ◮ Ví dụ 2 2: Ví dụ trích dẫn blog KTMT unsigned long * pStatReg = (unsigned long*) 0xE002C004; //Wait for the status register to become non-zero while(*pStatReg == 0) { } • Ta có ghi trạng thái pStatReg địa 0xE002c004 Ta cần phải so sánh giá trị ô nhớ với để làm điều kiện vịng lập while() • Đoạn code chạy sai ta bật tính tối ưu cho compiler Để hiểu rõ hơn, ta xem xét đoạn mã assembly sau: 10 LDR SUB LDR |L2.22| CMP BEQ LDR |L2.564| DCD r0,|L2.564| sp,sp,#0x10 r0,[r0,#0] r0,#0 |L2.22| r1,|L2.564| 0xe002c004 • Hàng 3: đầu tiên, giá trị 0xe002c004 tải vào ghi r0 (LDR r0,|L2.564|) Sau đó, giá trị địa chứa r0 với offset tải vào r0 (LDR r0,[r0,#0]) Tức lúc giá trị địa 0xe002c004 lưu vào r0 • Hàng 7: sau đó, giá trị kiểm tra với nhớ 0xe002c004 không tải lại vào r0 lần Nếu giá trị ban đầu địa 0xe002c004 bị rơi vào vịng lặp vô tận cho dù giá trị địa có cập nhật khác • Nếu chuyển biến pStatReg sang volatile, ta xem xét lại compiler: SPI ◮ Hàng 23: trả biến 16 bits nhận từ slave cách kết hợp bytes MSB LSB union struct Hàm transfer(void *, size_t): dùng để gửi liệu chứa buf với chiều dài muốn gửi count Giá trị nhận từ slave lưu ngược lại buf q trình gửi hồn tất 10 11 12 13 14 inline static void transfer(void *buf, size_t count) { if (count == 0) return; uint8_t *p = (uint8_t *)buf; SPDR = *p; while ( count > 0) { uint8_t out = *(p + 1); while (!(SPSR & _BV(SPIF))) ; uint8_t in = SPDR; SPDR = out; *p++ = in; } while (!(SPSR & _BV(SPIF))) ; *p = SPDR; } ◮ Hàng 2: count nghĩa khơng có byte cần gửi nên ngồi ◮ Hàng 3: tạo trỏ phụ p để lưu giá trị trỏ buf ngõ vào ◮ Hàng 4: gửi byte đệm ◮ Hàng 11: giá trị count trừ cho trước so sánh với 0, giá trị count lớn thực tiếp trình: • Hàng 6: chuẩn bị byte liệu để gửi lưu vào biến tạm out • Hàng 7: đợi q trình gửi byte hồn tất • Hàng 8: lưu giá trị liệu từ slave gửi qua master • Hàng 9: gửi giá trị chuẩn bị trước cách gán vào ghi liệu SPDR • Hàng 10: giá trị nhận lưu ngược trở lại buf ngõ vào tăng giá trị trỏ tạm lên ◮ Hàng 12 13: biến count khơng cịn liệu cần gửi, cần đợi byte liệu gửi xong lưu lại byte liệu nhận từ slave Hàm SPIClass::usingInterrupt Hàm để tắt ngắt q trình giao tiếp thơng qua SPI Điều giúp ngăn chặn xung đột trình sử dụng SPI bus Các ngắt tắt (disable) gọi hàm beginTransaction() bật lại (re-enable) gọi hàm endTransaction() Thông số đầu vào thông số sử dụng hàm attachInterrupt() Vì Atmega328P có ngắt ngồi INT0 INT1 nên lượt bỏ đoạn code không cần thiết 81 SPI 10 11 12 13 14 15 16 17 18 19 20 21 22 void SPIClass::usingInterrupt(uint8_t interruptNumber) { uint8_t mask = 0; uint8_t sreg = SREG; noInterrupts(); // Protect from a scheduler and prevent transactionBegin switch (interruptNumber) { #ifdef SPI_INT0_MASK case 0: mask = SPI_INT0_MASK; break; #endif #ifdef SPI_INT1_MASK case 1: mask = SPI_INT1_MASK; break; #endif default: interruptMode = 2; break; } interruptMask |= mask; if (!interruptMode) interruptMode = 1; SREG = sreg; } ◮ Hàng 3: biến mask dùng để lưu giá trị mặt nạ ngắt cần tắt ◮ ◮ ◮ ◮ ◮ trình giao tiếp SPI Hàng 5: ghi trạng thái lưu lại tắt ngắt Hàng 17: tùy thuộc vào giá trị nhập vào mà ngắt chọn lưu vào mask Hàng 18: giá trị mask cập nhật vào biến interruptMask lớp SPIClass Hàng 19 20: giá trị interruptMode chuyển thành chế độ Biến interruptMode dùng để lưu chế độ ngắt lớp SPIClass, giá trị khơng có ngắt tắt, ngắt interruptMask tắt, tất ngắt tắt Hàng 21: khôi phục lại giá trị ghi trạng thái Ta xem xét lại hàm beginTransaction() inline static void beginTransaction(SPISettings settings) { if (interruptMode > 0) { uint8_t sreg = SREG; noInterrupts(); 10 11 12 13 14 15 16 #ifdef SPI_AVR_EIMSK if (interruptMode == 1) { interruptSave = SPI_AVR_EIMSK; SPI_AVR_EIMSK &= ~interruptMask; SREG = sreg; } else #endif { interruptSave = sreg; } } 82 SPI SPCR = settings.spcr; SPSR = settings.spsr; 17 18 } 19 ◮ Hàng 4: interruptMode lớn lưu lại giá trị ghi trạng thái tắt ngắt ◮ Hàng 12: ghi mặt nạ ngắt định nghĩa (EIMSK - External Interrupt Mask Register): • Hàng 9: interruptMode lưu lại giá trị ghi EIMSK vào biến tạm interruptSave Xóa bit ghi EIMSK bit tương ứng interruptMask • Hàng 10: khơi phục lại ghi trạng thái Các ngắt khơi phục lại ngoại trừ ngắt ngồi clear ghi EIMSK • Hàng 13 15: interruptMode ghi EIMSK không định nghĩa lưu lại giá trị ghi trạng thái Tất ngắt bị tắt gọi hàm endTransaction() Các ngắt khôi phục lại gọi hàm endTransaction() inline static void endTransaction(void) { if (interruptMode > 0) { #ifdef SPI_AVR_EIMSK uint8_t sreg = SREG; #endif noInterrupts(); #ifdef SPI_AVR_EIMSK if (interruptMode == 1) { SPI_AVR_EIMSK = interruptSave; SREG = sreg; } else #endif { SREG = interruptSave; } 10 11 12 13 14 15 16 17 } 18 19 } ◮ Hàng 18: interruptMode lớn 0: • Hàng 5: ghi EIMSK định nghĩa, giá trị ghi trạng thái lưu lại • Hàng 7: Tắt ngắt để không gây xung đột làm việc với ghi • Hàng 14: ghi EIMSK định nghĩa interruptMode giá trị ghi EIMSK lưu trước (ở hàm beginTransaction) khôi phục Đồng thời ghi trạng thái khôi phục ngắt khơi phục 83 SPI • Hàng 15 17: interruptMode ghi EIMSK không định nghĩa khơi phục giá trị ghi trạng thái lưu trước (ở hàm beginTransaction) Hàm SPIClass::notUsingInterrupt() Hàm dùng để xóa bit biến interruptMask để ngắt khơng bị tắt q trình giao tiếp với SPI bus 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 void SPIClass::notUsingInterrupt(uint8_t interruptNumber) { // Once in mode we can't go back to without a proper reference count if (interruptMode == 2) return; uint8_t mask = 0; uint8_t sreg = SREG; noInterrupts(); // Protect from a scheduler and prevent transactionBegin switch (interruptNumber) { #ifdef SPI_INT0_MASK case 0: mask = SPI_INT0_MASK; break; #endif #ifdef SPI_INT1_MASK case 1: mask = SPI_INT1_MASK; break; #endif #ifdef SPI_INT2_MASK case 2: mask = SPI_INT2_MASK; break; #endif #ifdef SPI_INT3_MASK case 3: mask = SPI_INT3_MASK; break; #endif #ifdef SPI_INT4_MASK case 4: mask = SPI_INT4_MASK; break; #endif #ifdef SPI_INT5_MASK case 5: mask = SPI_INT5_MASK; break; #endif #ifdef SPI_INT6_MASK case 6: mask = SPI_INT6_MASK; break; #endif #ifdef SPI_INT7_MASK case 7: mask = SPI_INT7_MASK; break; #endif default: break; // this case can't be reached } interruptMask &= ~mask; if (!interruptMask) interruptMode = 0; SREG = sreg; } ◮ Hàng 5: interruptMode nghĩa tắt hết tất ngắt hàm khơng có tác dụng nên trả khơng làm thêm 84 SPI ◮ Hàng 8: khai báo biến tạm mask, lưu lại giá trị ghi trạng thái tắt ngắt ◮ Hàng 37: mặt nạ ngắt tương ứng với interruptNumber lưu vào mask ◮ Hàng 38: clear bit tương ứng với mask biến tạm interruptMask ◮ Hàng 39 40: biến tạm interruptMask (nghĩa khơng có ngắt tắt) chuyển interruptMode sang chế độ ◮ Hàng 41: khôi phục lại giá trị ghi trạng 85 INTERRUPT 9.1 Giới thiệu Ngắt xem phần quan trọng vi điều khiển Chương trình ngắt có quyền ưu tiên cao chương trình Khi có kiện ngắt xảy chương trình thực dừng lại, chương trình ngắt tương ứng gọi Sau chương trình ngắt thực xong quay lại xác chỗ chương trình dừng lại thực tiếp chương trình Vì lý này, cần phải thiết kế chương trình ngắt có thời gian thực thật ngắn để khơng làm ảnh hưởng đến chương trình 9.1 Giới thiệu 9.2 Các hàm thường dùng Hàm interrupt() Hàm noInterrupt() Hàm attachInterrupt() Hàm detachInterrupt() 9.3 Lưu ý làm việc với ngắt Mỗi vi điều khiển có bảng vectors ngắt (Interrupt Vectors Table) Hình 9.1: xxx ◮ Tại vector ngắt chứa địa chương trình ngắt tương ứng ◮ Khi có ngắt xảy ra, vi điều khiển lấy địa chương trình vector ngắt tương ứng nhảy vào chương trình để thực ◮ Trong bảng trên, ngắt RESET có độ ưu tiên cao Nếu có nhiều ngắt xảy lúc ngắt có độ ưu tiên cao thực trước khơng cịn ngắt quay lại thực chương trình Trong tảng Arduino, hầu hết ngắt quản lý thư viện tương ứng Nếu bạn tham khảo phần sách bạn thấy ngắt sử dụng hầu hết thư viện 86 87 87 87 87 89 89 INTERRUPT Ngắt mà người dùng sử dụng Arduino Uno ngắt chân digital tương ứng với ngắt INT0 INT1 Các nguồn sinh ngắt mức điện áp mức thấp (LOW), có cạnh lên (rising edge), có cạnh xuống (failing edge) có thay đổi từ mức cao xuống thấp thấp lên cao 9.2 Các hàm thường dùng Hàm interrupt() Mục đích hàm để bật ngắt toàn cục #define interrupts() sei() Hàm noInterrupt() Mục đích hàm dùng để tắt ngắt tồn cục #define noInterrupts() cli() Hàm attachInterrupt() Hàm định nghĩa file Winterrupts.c Hàm dùng để gắn địa chương trình ngắt vào ngắt ngồi Hàm ngắt mẫu đăng ký với ngắt void interruptRoutine() { } ◮ Hàm khơng có tham số đầu vào khơng có kết trả ◮ Vì chương trình ngắt nên giữ chương trình ngắt ◮ Nếu có biến ngắt giao tiếp với chương trình biết bắt buộc phải khai báo kiểu volatile 87 INTERRUPT void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) { if(interruptNum < EXTERNAL_NUM_INTERRUPTS) { intFunc[interruptNum] = userFunc; // // // // Configure the interrupt mode (trigger on low input, any change, rising edge, or falling edge) The mode constants were chosen to correspond to the configuration bits in the hardware register, so we simply shift the mode into place // Enable the interrupt 10 11 switch (interruptNum) { case 0: #if defined(EICRA) && defined(ISC00) && defined(EIMSK) EICRA = (EICRA & ~((1

Ngày đăng: 23/12/2022, 18:09

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w