Thực hiện bộ đếm thời gian phần mềm avr. AVR. Khoa Huân luyện. Đồng hồ hẹn giờ. Tạo khoảng thời gian bằng cách sử dụng bộ hẹn giờ

Chúng ta hãy xem cách tạo bộ hẹn giờ bằng tay của chính bạn trên bộ vi điều khiển ATmega8, mặc dù mã này khá dễ điều chỉnh cho các dòng AVR MK của các dòng khác. Đồng hồ hẹn giờ điện tử là một thiết bị cần thiết trong mọi lĩnh vực cần thực hiện một số hành động nhất định sau một khoảng thời gian cụ thể.

Điều khiển hẹn giờ chỉ bao gồm bốn nút:

- tăng giá trị của số;

- giảm giá trị của số;

- bắt đầu hẹn giờ;

- đặt lại hẹn giờ.

Bộ tạo tần số âm thanh có loa được sử dụng làm chỉ báo hoạt động của bộ hẹn giờ. Máy phát điện sẽ được khởi động bằng cách sử dụng công tắc bóng bán dẫn Q5, công tắc này được mở nhờ một điện thế dương đến từ cổng PC2 của bộ vi điều khiển.

Đơn giản hóa, bộ đếm thời gian hoạt động như sau. Sử dụng các nút “+” và “-” để đặt số giây cần thiết; Nút “bắt đầu” sẽ khởi động bộ hẹn giờ. Khi đồng hồ đếm ngược về 0, một điện thế cao sẽ xuất hiện trên chân PC2 của vi điều khiển ATmega8, sẽ mở Q5. Tiếp theo, công tắc bóng bán dẫn sẽ khởi động máy phát điện và phát ra âm thanh trong loa. Bộ hẹn giờ được đặt lại bằng cách nhấn nút “đặt lại”. Bộ tạo tần số âm thanh được lắp ráp trên hai bóng bán dẫn Q6 và Q7 có cấu trúc bán dẫn khác nhau. Nguyên lý hoạt động và mô tả mạch của các máy phát điện như vậy có thể được tìm thấy bằng cách nhấp vào.

Thuật toán hoạt động hẹn giờ trên vi điều khiển

Đồng hồ hẹn giờ của chúng tôi sẽ đếm ngược chính xác từng giây một, mặc dù bạn có thể đặt bất kỳ thời gian nào khác, ví dụ: phút, giờ, phần trăm giây, v.v.

Để hình thành khoảng thời gian một giây, chúng ta sẽ sử dụng bộ đếm thời gian đầu tiên của vi điều khiển ATmega8. Chúng tôi sẽ xác định tất cả các cài đặt của nó trong hàm bắt đầu. Đầu tiên, chúng tôi chia tần số hoạt động của bộ vi điều khiển 1000000 Hz cho 64 và nhận được tần số mới là 15625 Hz. Các bit CS10, CS11 và CS12 của thanh ghi TCCR1B chịu trách nhiệm về việc này. Tiếp theo, chúng ta kích hoạt ngắt trùng hợp và ghi một số nhị phân bằng số thập phân 15625 vào thanh ghi so sánh (cao và thấp).

bắt đầu vô hiệu (void)

TCCR1B &= ~(1<

TCCR1B |= (1<

TIMSK |= (1<

OCR1AH ​​​​= 0b00111101;

OCR1AL = 0b000001001; // thanh ghi so sánh 15625

TCNT1 = 0;

TCCR1B |= (1<

Khi đồng hồ đếm ngược đúng một giây, một ngắt sẽ được gọi. Trong phần thân của hàm ngắt, chúng ta sẽ giảm giá trị của biến đi một. Khi đạt đến 0, một điện thế cao sẽ xuất hiện ở đầu ra thứ hai của cổng C của vi điều khiển, nó sẽ mở công tắc bóng bán dẫn và khởi động máy phát điện, do đó chúng ta sẽ nghe thấy âm thanh trong loa.

ISR (TIMER1_COMPA_vect)

Z—;

Chúng tôi đã tìm ra bộ đếm vòng lặp của vòng lặp chính và phát hiện ra rằng nó hoàn toàn không phù hợp để đọc thời gian chính xác - tốc độ cửa trập thay đổi và rất khó để đếm. Phải làm gì?

Rõ ràng, chúng ta cần một số loại bộ đếm bên ngoài có thể đếm tích tắc bất kể hoạt động của bộ xử lý và bộ xử lý có thể nhìn thấy những gì đang tích tắc trong đó bất kỳ lúc nào. Hoặc để bộ đếm tạo ra các sự kiện tràn hoặc tràn - hãy nâng cờ hoặc tạo ngắt. Và phần trăm sẽ ngửi và xử lý nó.

Và có một bộ đếm như vậy, thậm chí không có một bộ đếm nào - đây là những bộ đếm thời gian ngoại vi. Có thể có một vài trong số chúng trong AVR và thậm chí có độ sâu bit khác nhau. ATmega16 có ba, ATmega128 có bốn. Và trong các MK mới của dòng AVR có thể còn nhiều hơn nữa, tôi không nhận ra.

Hơn nữa, bộ đếm thời gian có thể không chỉ là một bộ đếm ngu ngốc; bộ đếm thời gian là một trong những thiết bị ngoại vi phức tạp nhất (về chức năng thay thế).

Bộ hẹn giờ có thể làm gì?

  • Đánh dấu ở các tốc độ khác nhau, đếm thời gian
  • Đếm xung đến từ bên ngoài (chế độ đếm)
  • Đánh dấu từ thạch anh ngoài ở tần số 32768Hz
  • Tạo một số loại tín hiệuPWM
  • Đưa ra các ngắt (nửa tá sự kiện khác nhau) và đặt cờ

Các bộ định thời khác nhau có chức năng khác nhau và độ sâu bit khác nhau. Xem bảng dữ liệu để biết thêm chi tiết.

Nguồn đánh dấu bộ đếm thời gian
Bộ định thời/bộ đếm (sau đây tôi sẽ gọi là T/C) đếm các xung đồng hồ từ bộ tạo xung nhịp tích hợp hoặc từ đầu vào đếm.

Nhìn kỹ sơ đồ chân của ATmega16 bạn có thấy chân T1 và T0 ở đó không?

Vì vậy, đây là các đầu vào đếm Bộ hẹn giờ 0 và Bộ hẹn giờ 1. Với các cài đặt phù hợp, T/S sẽ đếm cạnh đầu (cạnh từ 0-1) hoặc cạnh sau (cạnh 1-0) của xung đến các đầu vào này.

Điều chính là tần số của các xung đến không vượt quá tần số xung nhịp của bộ xử lý, nếu không nó sẽ không có thời gian để xử lý các xung.

Ngoài ra, T/C2 còn có khả năng hoạt động ở chế độ không đồng bộ. Nghĩa là, T/S không tính các xung đồng hồ của bộ xử lý, không phải các xung đến các chân mà là các xung của bộ dao động của chính nó, được cung cấp bởi một thạch anh riêng biệt. Để thực hiện việc này, T/C2 có đầu vào TOSC1 và TOSC2, trên đó bạn có thể gắn bộ cộng hưởng thạch anh.

Tại sao điều này lại cần thiết? Có, ít nhất hãy tổ chức một chiếc đồng hồ thời gian thực. Tôi treo một đồng hồ thạch anh trên chúng ở tần số 32768 Hz và đếm thời gian - 128 lần tràn sẽ xảy ra mỗi giây (vì T/C2 là 8 bit). Vì vậy, một lần tràn là 1/128 giây. Hơn nữa, bộ định thời không dừng khi ngắt tràn đang được xử lý mà vẫn tiếp tục đếm. Vì vậy, đồng hồ là một làn gió!

Bộ chia trước
Nếu bộ đếm thời gian đếm các xung từ bộ tạo xung nhịp hoặc từ bộ tạo xung bên trong nó thì chúng vẫn có thể được truyền qua bộ đếm gộp trước.

Nghĩa là, ngay cả trước khi vào thanh ghi đếm, tần số xung sẽ được chia. Bạn có thể chia cho 8, 32, 64, 128, 256, 1024. Vì vậy, nếu bạn đặt một đồng hồ thạch anh trên T/C2 và chuyển nó qua bộ đếm gộp trước ở 128, thì bộ đếm thời gian của bạn sẽ tích tắc với tốc độ một tích tắc mỗi giây.

Thoải mái! Cũng rất thuận tiện khi sử dụng bộ đếm gộp khi bạn chỉ cần có một khoảng thời gian lớn và nguồn tích tắc duy nhất là bộ tạo xung nhịp bộ xử lý ở tần số 8 MHz, bạn sẽ cảm thấy mệt mỏi khi đếm các megahertz này, nhưng nếu bạn chuyển nó qua bộ đếm gộp trước , ở mức 1024 thì mọi thứ sẽ vui hơn nhiều.

Nhưng có một điểm đặc biệt ở đây, thực tế là nếu chúng ta khởi chạy T/S với một loại bộ đếm gộp tàn bạo nào đó, chẳng hạn như ở 1024, thì tích tắc đầu tiên vào thanh ghi đếm sẽ không nhất thiết phải đến sau 1024 xung.

Nó phụ thuộc vào trạng thái của bộ đếm gộp trước, điều gì sẽ xảy ra nếu vào thời điểm chúng ta bật nó lên, nó đã đếm tới gần 1024? Điều này có nghĩa là sẽ có tích tắc ngay lập tức. Bộ đếm gộp trước hoạt động mọi lúc, bất kể bộ hẹn giờ có được bật hay không.

Do đó, bộ đếm gộp trước có thể và nên được đặt lại. Bạn cũng cần tính đến thực tế là bộ đếm gộp trước đều giống nhau cho tất cả các bộ đếm, vì vậy khi đặt lại nó, bạn cần tính đến thực tế là bộ đếm thời gian khác sẽ mất thời gian cho đến tích tắc tiếp theo và nó có thể sai chính xác. cách này.

Ví dụ: bộ hẹn giờ đầu tiên hoạt động ở chân 1:64 và bộ hẹn giờ thứ hai ở chân 1:1024 của bộ đếm gộp trước. Cái thứ hai gần như đạt tới 1024 trong bộ đếm gộp trước và bây giờ sẽ có tích tắc hẹn giờ, nhưng sau đó bạn đã đi và đặt lại bộ đếm gộp trước để bắt đầu chính xác bộ đếm thời gian đầu tiên từ đầu. Chuyện gì sẽ xảy ra? Đúng vậy, bộ chia thứ hai sẽ ngay lập tức đặt lại về 0 (bộ chia tỷ lệ trước cũng vậy, nó có một thanh ghi) và bộ đếm thời gian thứ hai sẽ phải đợi thêm 1024 chu kỳ xung nhịp nữa để có được xung mong muốn!

Và nếu bạn đặt lại bộ đếm gộp trước trong vòng lặp, vì lợi ích của bộ đếm thời gian đầu tiên, thường xuyên hơn một lần trong mỗi 1024 chu kỳ đồng hồ, thì bộ đếm thời gian thứ hai sẽ không bao giờ tích tắc và bạn sẽ đập đầu vào bàn, cố gắng hiểu tại sao bộ hẹn giờ thứ hai của bạn không hoạt động, mặc dù vậy.

Để đặt lại bộ đếm gộp trước, chỉ cần ghi bit PSR10 vào thanh ghi SFIOR. Bit PSR10 sẽ được thiết lập lại tự động trong chu kỳ xung nhịp tiếp theo.

Đăng ký tài khoản
Toàn bộ kết quả của sự hành hạ kể trên được tích lũy vào thanh ghi TCNTx, trong đó x là số đếm. nó có thể là tám bit hoặc mười sáu bit, trong trường hợp đó, nó bao gồm hai thanh ghi TCNTxH và TCNTxL - byte cao và byte thấp tương ứng.

Và có một nhược điểm ở đây, nếu bạn cần đặt một số vào một thanh ghi tám bit thì không có vấn đề gì OUT TCNT0, Rx và không có đinh, thì với các thanh ghi hai byte, bạn sẽ phải thử lại.

Và vấn đề là bộ đếm thời gian đếm độc lập với bộ xử lý, vì vậy chúng ta có thể đặt một byte đầu tiên, nó sẽ bắt đầu đếm, sau đó là byte thứ hai và quá trình tính toán lại sẽ bắt đầu tính đến byte thứ hai.

Bạn có cảm thấy như tôi đang hòa hợp không? Đây! Bộ đếm thời gian là một thiết bị chính xác nên các thanh ghi đếm của nó phải được tải cùng lúc! Nhưng bằng cách nào? Và các kỹ sư từ Atmel đã giải quyết vấn đề một cách đơn giản:
Thanh ghi cao (TCNTxH) được ghi vào đầu tiên trong thanh ghi TEMP. Sổ đăng ký này hoàn toàn chính thức và chúng tôi không thể truy cập được bằng mọi cách.

Kết quả cuối cùng là: Chúng tôi ghi byte cao vào thanh ghi TEMP (đối với chúng tôi đây là một TCNTxH), sau đó ghi byte thấp. Tại thời điểm này, giá trị chúng tôi ghi lại trước đó sẽ được nhập vào TCNTxH thực. Nghĩa là, hai byte, cao và thấp, được ghi đồng thời! Bạn không thể thay đổi thứ tự! Cách duy nhất

Nó trông như thế này:

CLI ; Chúng tôi nghiêm cấm sự gián đoạn! NGOÀI TCNT1H,R16 ; Byte quan trọng nhất được ghi đầu tiên vào TEMP OUT TCNT1L,R17 ; Và bây giờ tôi đã đăng ký cả lớp cấp 3 và cấp 2! SEI; Kích hoạt ngắt

Tại sao vô hiệu hóa ngắt? Có, để sau khi ghi byte đầu tiên, chương trình không vô tình chạy nhanh mà không bị gián đoạn và sau đó ai đó sẽ cưỡng hiếp bộ đếm thời gian của chúng tôi. Sau đó, trong sổ đăng ký của nó, nó sẽ không phải là những gì chúng tôi đã gửi ở đây (hoặc trong phần ngắt), mà là cái quái gì vậy. Vì vậy, hãy cố gắng bắt một lỗi như vậy sau! Nhưng nó có thể xuất hiện vào thời điểm không thích hợp nhất, nhưng bạn sẽ không nắm bắt được nó, bởi vì sự gián đoạn gần như là một biến số ngẫu nhiên. Vì vậy những khoảnh khắc như vậy cần được giải quyết ngay lập tức.

Mọi thứ đều được đọc theo cùng một cách, chỉ theo thứ tự ngược lại. Đầu tiên, byte thấp (trong khi byte cao được chuyển vào TEMP), sau đó là byte cao. Điều này đảm bảo rằng chúng ta đang đếm chính xác byte hiện có trong thanh ghi đếm chứ không phải byte đang chạy trong khi chúng ta chọn byte byte đó từ thanh ghi đếm.

Thanh ghi điều khiển
Tôi sẽ không mô tả tất cả các chức năng của bộ hẹn giờ, nếu không nó sẽ trở thành một chuyên luận quá sức... Tốt hơn là nên nói về chức năng chính - chức năng đếm, còn tất cả các loại máy phát điện xung lực và các loại máy phát điện khác sẽ có trong một bài viết khác. Vì vậy, hãy kiên nhẫn hoặc nhai kỹ bảng dữ liệu, nó cũng hữu ích.

Vậy thanh ghi chính là TCCRx
Đối với T/C0 và T/C2, đây lần lượt là TCCR0 và TCCR2, và đối với T/C1, đây là TCCR1B

Hiện tại, chúng tôi chỉ quan tâm đến ba bit đầu tiên của thanh ghi này:
CSx2.. CSx0, thay x bằng số hẹn giờ.
Họ chịu trách nhiệm thiết lập bộ đếm gộp trước và nguồn đồng hồ.

Các bộ định thời khác nhau sẽ hơi khác một chút, vì vậy tôi sẽ mô tả các bit CS02..CS00 chỉ dành cho bộ định thời 0

  • 000 - đồng hồ đã dừng
  • 001 - bộ đếm gộp trước bằng 1, nghĩa là tắt. bộ đếm thời gian đếm xung đồng hồ
  • 010 - bộ đếm gộp trước là 8, tần số xung nhịp được chia cho 8
  • 011 - bộ đếm gộp trước là 64, tần số xung nhịp được chia cho 64
  • 100 - bộ đếm gộp trước là 256, tần số xung nhịp được chia cho 256
  • 101 - bộ đếm gộp trước là 1024, tần số xung nhịp được chia cho 1024
  • 110 - xung đồng hồ đến từ chân T0 khi chuyển từ 1 sang 0
  • 111 - xung đồng hồ đến từ chân T0 khi chuyển từ 0 sang 1

Ngắt
Mọi sự kiện phần cứng đều có sự gián đoạn và bộ đếm thời gian cũng không ngoại lệ. Ngay khi xảy ra tình trạng tràn hoặc một số sự kiện gây tò mò khác, một ngắt sẽ ngay lập tức xuất hiện.

Các thanh ghi TIMSK và TIFR chịu trách nhiệm về các ngắt từ bộ định thời. Và các AVR mát hơn, chẳng hạn như ATMega128, cũng có ETIFR và ETIMSK - một kiểu tiếp tục, vì sẽ có nhiều bộ tính giờ hơn ở đó.

TIMSK là một thanh ghi mặt nạ. Nghĩa là, các bit chứa trong nó cho phép ngắt cục bộ. Nếu bit được đặt, ngắt cụ thể sẽ được kích hoạt. Nếu bit bằng 0 thì ngắt này được bao phủ bởi một cái chậu. Theo mặc định, tất cả các bit đều bằng 0.

Hiện tại chúng tôi chỉ quan tâm đến các ngắt tràn. Các bit chịu trách nhiệm về chúng

  • TOIE0 - quyền ngắt khi tràn bộ đếm thời gian 0
  • TOIE1 - quyền ngắt khi tràn bộ đếm thời gian 1
  • TOIE2 - quyền ngắt khi tràn bộ đếm thời gian 2

Chúng ta sẽ nói về các tính năng khác và ngắt hẹn giờ sau, khi chúng ta xem xét xung điện xung.

Thanh ghi TIFR trực tiếp là thanh ghi cờ. Khi một số gián đoạn được kích hoạt, một lá cờ sẽ bật lên cho biết rằng chúng tôi bị gián đoạn. Cờ này được thiết lập lại bằng phần cứng khi chương trình rời khỏi vectơ. Nếu các ngắt bị vô hiệu hóa thì cờ sẽ vẫn ở đó cho đến khi các ngắt được kích hoạt và chương trình chuyển sang trạng thái ngắt.

Để ngăn điều này xảy ra, cờ có thể được đặt lại theo cách thủ công. Để làm điều này, bạn cần viết số 1 vào thanh ghi TIFR!

Bây giờ hãy chết tiệt
Chà, hãy thiết kế lại chương trình để hoạt động với bộ hẹn giờ. Hãy giới thiệu một bộ đếm thời gian chương trình. Cơ quan thùng sẽ vẫn như vậy, hãy để nó tích tắc. Và chúng ta sẽ thêm biến thứ hai, cũng là bốn byte:

ORG $010 RETI ; (TIMER1 OVF) Tràn bộ đếm thời gian/bộ đếm 1 .ORG $012 RJMP Hẹn giờ0_OV ; (TIMER0 OVF) Tràn bộ đếm thời gian/bộ đếm0 .ORG $014 RETI ; (SPI,STC) Hoàn tất truyền nối tiếp

Hãy thêm một trình xử lý ngắt cho trường hợp tràn bộ đếm thời gian 0 vào phần Ngắt. Vì macro đánh dấu của chúng tôi hoạt động tích cực với các thanh ghi và cờ bị hỏng, nên trước tiên chúng tôi cần lưu toàn bộ nội dung này vào ngăn xếp:

Nhân tiện, hãy tạo một macro khác để đẩy thanh ghi cờ SREG vào ngăn xếp và macro thứ hai lấy nó từ đó.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

Là một tác dụng phụ, nó cũng giữ lại R16, hãy nhớ rằng :)

1 2 3 4 5 6 7 8 9 10 11 12 13 Hẹn giờ0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Hẹn giờ0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Bây giờ khởi tạo bộ đếm thời gian. Thêm nó vào phần Internal Hardware Init.

; Ban đầu phần cứng bên trong ========================================== SETB DDRD,4,R16 ; DDRD.4 = 1 BỘ DDRD,5,R16 ; DDRD.5 = 1 BỘ DDRD,7,R16 ; DDRD.7 = 1 SETB PORTD,6,R16 ; Đầu ra PD6 đến đầu vào kéo lên CLRB DDRD,6,R16 ; Để đọc nút SETB TIMSK,TOIE0,R16 ; Cho phép ngắt hẹn giờ OUTI TCCR0,1<

Tất cả những gì còn lại là viết lại khối so sánh của chúng ta và tính toán lại số. Bây giờ mọi thứ đều đơn giản, một tích tắc một ô. Không có bất kỳ vấn đề nào với độ dài mã khác nhau. Trong một giây ở tần số 8 MHz, phải thực hiện 8 triệu tích tắc. Trong hex, đây là 7A 12 00, có tính đến byte thấp là TCNT0, sau đó 7A 12 được để lại cho bộ đếm của chúng ta và cả hai byte cao nhất 00 00, chúng không cần phải kiểm tra. Không cần phải che đậy; dù sao thì chúng ta cũng sẽ đặt lại bộ đếm thời gian sau.

Chỉ có một vấn đề - byte thấp, vấn đề trong bộ đếm thời gian. Nó tích tắc từng tích tắc và gần như không thể kiểm tra sự tuân thủ. Bởi vì sự khác biệt nhỏ nhất và điều kiện so sánh sẽ xuất hiện trong NoMatch, nhưng hãy đoán nó sao cho việc kiểm tra giá trị của nó trùng với bước cụ thể này... Việc rút kim ra khỏi đống cỏ khô trong lần thử đầu tiên một cách ngẫu nhiên sẽ dễ dàng hơn.

Vì vậy, độ chính xác trong trường hợp này bị hạn chế - bạn cần có thời gian để kiểm tra giá trị trước khi nó rời khỏi phạm vi. Trong trường hợp này, để đơn giản, phạm vi sẽ là 255 - giá trị của byte thấp, giá trị trong bộ đếm thời gian.

Sau đó, giây thứ hai của chúng tôi được cung cấp độ chính xác 8.000.000 cộng hoặc trừ 256 chu kỳ. Sai số không lớn, chỉ 0,003%.

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 ; Chính ===================================================== ================= ========= Chính: SBIS PIND,6 ; Nếu nhấn nút - chuyển tiếp RJMP BT_Push SETB PORTD,5 ; Hãy bật đèn LED2 CLRB PORTD,4 ; Tắt LED1 Tiếp theo: LDS R16,TCNT ; Nạp số vào các thanh ghi LDS R17,TCNT+1 CPI R16.0x12 ; Hãy so sánh từng byte. Byte đầu tiên BRCS NoMatch ; Nếu nó ít hơn, có nghĩa là nó không trúng. CPI R17.0x7A ; Byte thứ hai BRCS NoMatch ; Nếu nó ít hơn, có nghĩa là nó không trúng. ; Nếu nó khớp thì chúng tôi thực hiện hành động So khớp: INVB PORTD,7,R16,R17 ; Đảo ngược LED3; Bây giờ chúng ta cần đặt lại bộ đếm, nếu không thì trong cùng một lần lặp của vòng lặp chính; chúng ta sẽ đến đây nhiều lần - bộ đếm thời gian sẽ không có thời gian để đạt tới 255 giá trị; sao cho số trong hai byte đầu tiên của bộ đếm thay đổi và điều kiện được kích hoạt. ; Tất nhiên, bạn có thể bỏ qua điều này bằng một cờ bổ sung, nhưng việc đặt lại bộ đếm sẽ dễ dàng hơn :) CLR R16 ; Chúng tôi không cần CLI; Truy cập vào một biến nhiều byte; đồng thời từ sự gián đoạn và nền tảng; Truy cập nguyên tử là cần thiết. Vô hiệu hóa các ngắt OUTU TCNT0,R16 ; Zero đến thanh ghi bộ đếm thời gian STS TCNT, R16 ; Số 0 ở byte đầu tiên của bộ đếm trong RAM STS TCNT+1,R16 ; Số 0 ở byte thứ hai của bộ đếm trong RAM STS TCNT+2,R16 ; Số 0 ở byte thứ ba của bộ đếm trong RAM STS TCNT+3,R16 ; Số 0 trong byte đầu tiên của bộ đếm trong RAM SEI; Hãy kích hoạt lại các ngắt. ; Nếu không khớp thì chúng tôi không làm :) NoMatch: NOP INCM CCNT ; Bộ đếm chu kỳ đang tích tắc; Ngay cả khi nó không được sử dụng. BT_Push chính của JMP: SETB PORTD,4 ; Hãy thắp sáng LED1 CLRB PORTD,5; Tắt LED2 RJMP Tiếp theo; Cuối chính ===================================================== ================= =====

; Chính ===================================================== ================= ========= Chính: SBIS PIND,6 ; Nếu nhấn nút - chuyển tiếp RJMP BT_Push SETB PORTD,5 ; Hãy bật đèn LED2 CLRB PORTD,4 ; Tắt LED1 Tiếp theo: LDS R16,TCNT ; Nạp số vào các thanh ghi LDS R17,TCNT+1 CPI R16.0x12 ; Hãy so sánh từng byte. Byte đầu tiên BRCS NoMatch ; Nếu nó ít hơn, có nghĩa là nó không trúng. CPI R17.0x7A ; Byte thứ hai BRCS NoMatch ; Nếu nó ít hơn, có nghĩa là nó không trúng. ; Nếu nó khớp thì chúng tôi thực hiện hành động So khớp: INVB PORTD,7,R16,R17 ; Đảo ngược LED3; Bây giờ chúng ta cần đặt lại bộ đếm, nếu không thì trong cùng một lần lặp của vòng lặp chính; chúng ta sẽ đến đây nhiều lần - bộ đếm thời gian sẽ không có thời gian để đạt tới 255 giá trị; sao cho số trong hai byte đầu tiên của bộ đếm thay đổi và điều kiện được kích hoạt. ; Tất nhiên, bạn có thể bỏ qua điều này bằng một cờ bổ sung, nhưng việc đặt lại bộ đếm sẽ dễ dàng hơn :) CLR R16 ; Chúng tôi không cần CLI; Truy cập vào một biến nhiều byte; đồng thời từ sự gián đoạn và nền tảng; Truy cập nguyên tử là cần thiết. Vô hiệu hóa các ngắt OUTU TCNT0,R16 ; Zero đến thanh ghi bộ đếm thời gian STS TCNT, R16 ; Số 0 ở byte đầu tiên của bộ đếm trong RAM STS TCNT+1,R16 ; Số 0 ở byte thứ hai của bộ đếm trong RAM STS TCNT+2,R16 ; Số 0 ở byte thứ ba của bộ đếm trong RAM STS TCNT+3,R16 ; Số 0 trong byte đầu tiên của bộ đếm trong RAM SEI; Hãy kích hoạt lại các ngắt. ; Nếu không khớp thì chúng tôi không làm :) NoMatch: NOP INCM CCNT ; Bộ đếm chu kỳ đang tích tắc; Ngay cả khi nó không được sử dụng. BT_Push chính của JMP: SETB PORTD,4 ; Hãy thắp sáng LED1 CLRB PORTD,5; Tắt LED2 RJMP Tiếp theo; Cuối chính ==================================================== ================= =====

Đây là những gì nó trông giống như trong hành động

Và nếu chúng ta cần nhấp nháy diode thứ hai với chu kỳ khác, thì chúng ta có thể đặt một biến khác vào chương trình một cách an toàn và trong trình xử lý ngắt hẹn giờ, chúng ta có thể tăng hai biến cùng một lúc. Kiểm tra từng cái một trong vòng lặp chương trình chính.

Bạn có thể tối ưu hóa quá trình xác minh thêm một chút. Lam no nhanh hơn.

Bạn chỉ cần làm cho tài khoản không lên mà xuống. Những thứ kia. Chúng ta nạp một số vào một biến và bắt đầu giảm dần nó khi ngắt. Và ở đó, trong trình xử lý, chúng tôi kiểm tra xem nó có bằng 0 không. Nếu bằng 0 thì đặt cờ trong bộ nhớ. Và chương trình nền của chúng tôi bắt được lá cờ này và khởi chạy hành động, đồng thời đặt lại tốc độ màn trập.

Nhưng nếu bạn cần chính xác hơn thì sao? Chà, chỉ có một tùy chọn - sử dụng xử lý sự kiện trực tiếp trong trình xử lý ngắt và điều chỉnh giá trị trong TCNT:TCNT0 mỗi lần để gián đoạn xảy ra chính xác vào đúng thời điểm.

Bộ đếm thời gian là một trong những tài nguyên phổ biến nhất của vi điều khiển AVR. Mục đích chính của nó là đếm ngược các khoảng thời gian được chỉ định. Ngoài ra, bộ đếm thời gian có thể thực hiện một số chức năng bổ sung, chẳng hạn như tạo tín hiệu xung, đếm thời lượng và số lượng xung đến. Với mục đích này, có các chế độ hoạt động đặc biệt của bộ đếm thời gian.

Tùy thuộc vào kiểu vi điều khiển, số lượng bộ định thời và bộ chức năng của chúng có thể khác nhau. Ví dụ: bộ vi điều khiển Atmega16 có ba bộ đếm thời gian - hai bộ đếm thời gian 8 bit T0 và T2 và một bộ đếm thời gian 16 bit - T1. Trong bài viết này, sử dụng ATmega16 làm ví dụ, chúng ta sẽ xem xét cách sử dụng bộ đếm thời gian T0.

Ghim được sử dụng

Bộ đếm thời gian T0 sử dụng hai chân của vi điều khiển ATmega16. Chân T0 (PB0) là đầu vào đồng hồ bên ngoài. Ví dụ, nó có thể được sử dụng để đếm xung. Chân OC0 (PB3) là đầu ra của mạch so sánh bộ định thời-bộ đếm. Chân này có thể tạo ra tín hiệu sóng vuông hoặc tín hiệu xung xung bằng bộ định thời. Nó cũng có thể thay đổi trạng thái một cách đơn giản khi mạch so sánh được kích hoạt, nhưng chúng ta sẽ nói về điều đó sau.


Các chân T0 và OC0 chỉ được kích hoạt khi có cài đặt hẹn giờ thích hợp; ở trạng thái bình thường đây là các chân có mục đích chung.

Thanh ghi bộ đếm thời gian T0

Mặc dù nhàm chán nhưng thanh ghi là thứ mà không có nó thì không thể lập trình bộ vi điều khiển, tất nhiên, nếu bạn không ngồi vững trên Arduino. Vì vậy, bộ định thời T0 có ba thanh ghi:

Thanh ghi đếm TCNT0,
- thanh ghi so sánh OCR0,
- Thanh ghi cấu hình TCCR0.

Ngoài ra, còn có ba thanh ghi nữa liên quan đến cả ba bộ định thời ATmega16:

Thanh ghi cấu hình TIMSK,
- Thanh ghi trạng thái TIFR
- thanh ghi chức năng đặc biệt SFIOR

Hãy bắt đầu với cách đơn giản nhất.

Đây là một thanh ghi đếm 8 bit. Khi bộ định thời đang chạy, mỗi xung đồng hồ sẽ thay đổi giá trị của TCNT0 một. Tùy thuộc vào chế độ hoạt động của bộ định thời, thanh ghi đếm có thể tăng hoặc giảm.
Thanh ghi TCNT0 có thể được đọc và ghi. Cái sau được sử dụng khi bạn cần đặt giá trị ban đầu của nó. Khi bộ định thời đang chạy, không nên thay đổi nội dung của TCNT0 vì điều này sẽ chặn mạch so sánh trong một chu kỳ xung nhịp.

Đây là thanh ghi so sánh 8 bit. Giá trị của nó được so sánh liên tục với thanh ghi đếm TCNT0,và nếu khớp, bộ hẹn giờ có thể thực hiện một số hành động - gây gián đoạn, thay đổi trạng thái của chân OC0, v.v. tùy thuộc vào chế độ hoạt động.

TCCR0 (Thanh ghi điều khiển bộ đếm thời gian/bộ đếm)


Đây là thanh ghi cấu hình bộ đếm thời gian T0 và xác định nguồn xung nhịp bộ định thời, hệ số đặt trước, chế độ bộ đếm thời gian T0 và hoạt động của chân OC0.Về cơ bản là đăng ký quan trọng nhất.

Chút ít CS02, CS01, CS00 (Chọn đồng hồ)- xác định nguồn tần số xung nhịp cho bộ định thời T0 và đặt hệ số bộ đếm gộp trước. Tất cả các trạng thái có thể được mô tả trong bảng dưới đây.


Như bạn có thể thấy, bộ đếm thời gian có thể bị dừng, có thể được bấm giờ từ tần số bên trong và cũng có thể được bấm giờ từ tín hiệu ở chân T0.

Chút ít WGM10, WGM00 (Chế độ tạo sóng)- xác định chế độ hoạt động của bộ đếm thời gian T0. Có thể có bốn trong số chúng - chế độ bình thường (bình thường), đặt lại bộ hẹn giờ khi trùng khớp (CTC) và hai chế độ điều chế độ rộng xung (FastPWM và Phase CorrectPWM). Tất cả các giá trị có thể được mô tả trong bảng bên dưới.

Chúng tôi sẽ phân tích các chế độ trong mã chi tiết hơn. Bây giờ tất cả các sắc thái vẫn sẽ không được ghi nhớ.

Chút ít COM01, COM00 (So sánh chế độ đầu ra khớp)- xác định hoạt động của đầu ra OC0. Nếu bất kỳ bit nào trong số này được đặt thành 1 thì chân OC0 sẽ ngừng hoạt động như một chân đa năng thông thường và được kết nối với mạch so sánh bộ định thời bộ đếm T0. Tuy nhiên, nó vẫn phải được cấu hình làm đầu ra.
Hoạt động của chân OC0 phụ thuộc vào chế độ hoạt động của bộ đếm thời gian T0. Trong chế độ bình thường và CTC, chân OC0 hoạt động giống nhau, nhưng trong chế độ điều chế độ rộng xung, hoạt động của nó khác nhau. Bây giờ chúng ta đừng bận tâm với tất cả các tùy chọn này và phân tích các bảng cho từng chế độ, hãy để phần này cho phần thực tế.

Và bit cuối cùng của thanh ghi TCCR0 là bit FOC0 (So sánh đầu ra lực).Bit này nhằm mục đích buộc thay đổi trạng thái của chân OC0. Nó chỉ hoạt động ở chế độ Bình thường và CTC. Khi bit FOC0 được đặt thành 1, trạng thái đầu ra thay đổi theo giá trị của bit COM01, COM00. Bit FOC0 không gây ra ngắt hoặc đặt lại bộ đếm thời gian ở chế độ CTC.

TIMSK (Đăng ký mặt nạ ngắt bộ đếm thời gian/bộ đếm)


Một thanh ghi chung cho cả ba bộ định thời ATmega16, nó chứa các cờ cho phép ngắt. Bộ định thời T0 có thể gây ra ngắt khi thanh ghi đếm TCNT0 bị tràn và khi thanh ghi đếm trùng với thanh ghi so sánh OCR0. Theo đó, hai bit được dành riêng cho bộ định thời T0 trong thanh ghi TIMSK - TOIE0 và OCIE0. Các bit còn lại đề cập đến các bộ tính giờ khác.

TOIE0- Giá trị bit thứ 0 vô hiệu hóa sự gián đoạn do sự kiện tràn, 1 - cho phép nó.
OCIE0- Giá trị 0 vô hiệu hóa các ngắt do sự kiện trùng hợp và 1 cho phép ngắt.

Đương nhiên, các ngắt sẽ chỉ được gọi nếu bit cho phép ngắt toàn cục được đặt - bit I của thanh ghi SREG.

TIFR (Thanh ghi cờ ngắt bộ đếm thời gian/bộ đếm 0)


Một thanh ghi chung cho cả ba bộ đếm thời gian. Chứa các cờ trạng thái được đặt khi sự kiện xảy ra. Đối với bộ định thời T0, đây là hiện tượng tràn thanh ghi TCNT0 và sự trùng hợp của thanh ghi đếm với thanh ghi so sánh OCR0.

Nếu tại những thời điểm này, các ngắt được bật trong thanh ghi TIMSK và bit I được đặt, bộ vi điều khiển sẽ gọi bộ xử lý tương ứng.
Các cờ này sẽ tự động bị xóa khi trình xử lý ngắt bắt đầu. Điều này cũng có thể được thực hiện theo chương trình bằng cách ghi 1 vào cờ tương ứng.

TOV0- đặt thành 1 khi thanh ghi đếm tràn.
OCF0- đặt thành 1 khi thanh ghi đếm trùng với thanh ghi so sánh

SFIOR (Thanh ghi IO chức năng đặc biệt)


Về nguyên tắc, người mới bắt đầu có thể thậm chí không biết về thanh ghi này; một trong các bit của nó đặt lại bộ đếm nhị phân 10 bit, bộ đếm này chia tần số đầu vào cho bộ định thời T0 và bộ định thời T1.

Việc đặt lại xảy ra khi bit được đặt PSR10 (Bộ đếm thời gian đặt lại bộ đếm gộp trước/Bộ đếm 1 và Bộ định thời/Bộ đếm 0) trên mỗi đơn vị.

Phần kết luận




MCU ATMega16 có ba bộ định thời/bộ đếm - hai bộ định thời 8 bit (Bộ định thời/Bộ đếm0, Bộ định thời/Bộ đếm2) và một bộ định thời 16 bit (Bộ định thời/Bộ đếm1). Mỗi thanh ghi chứa các thanh ghi đặc biệt, một trong số đó là thanh ghi đếm TCNTn (n là số 0, 1 hoặc 2). Mỗi lần bộ xử lý thực hiện một lệnh, nội dung của thanh ghi này sẽ tăng thêm một (cứ sau 8, 64, 256 hoặc 1024 chu kỳ xung nhịp). Đó là lý do tại sao nó được gọi là đếm được. Ngoài ra, còn có một thanh ghi so sánh OCRn (Thanh ghi so sánh đầu ra), trong đó chúng ta có thể tự viết bất kỳ số nào. Bộ đếm 8 bit có các thanh ghi 8 bit. Khi chương trình thực thi, nội dung của TCNTn tăng dần và đến một lúc nào đó nó sẽ trùng với nội dung của OCRn. Sau đó (nếu các tham số đặc biệt được chỉ định) trong thanh ghi cờ ngắt TIFR (Thanh ghi cờ ngắt bộ đếm thời gian/bộ đếm), một trong các bit trở thành bằng một và bộ xử lý, khi nhìn thấy yêu cầu ngắt, ngay lập tức ngừng thực hiện vòng lặp vô tận và đi tiếp. để phục vụ ngắt hẹn giờ. Sau đó, quá trình này được lặp lại.

Dưới đây là sơ đồ thời gian của chế độ CTC (Xóa bộ đếm thời gian so sánh). Trong chế độ này, thanh ghi đếm sẽ bị xóa khi nội dung của TCNTn và OCRn khớp nhau và thời gian gọi ngắt thay đổi tương ứng.

Đây không phải là chế độ hoạt động duy nhất của bộ đếm thời gian/bộ đếm. Bạn không cần phải xóa thanh ghi đếm tại thời điểm khớp, đây sẽ là chế độ tạo điều chế độ rộng xung mà chúng ta sẽ xem xét trong bài viết tiếp theo. Bạn có thể thay đổi hướng đếm, tức là nội dung của thanh ghi đếm sẽ giảm khi chương trình chạy. Cũng có thể đếm không phải bằng số lượng lệnh được bộ xử lý thực thi mà bằng số lượng thay đổi về mức điện áp trên “chân” T0 hoặc T1 (chế độ bộ đếm); bạn có thể tự động mà không cần sự tham gia của bộ xử lý , thay đổi trạng thái của các chân OCn tùy theo trạng thái của bộ định thời. Bộ hẹn giờ/Bộ đếm1 có thể so sánh hai kênh cùng một lúc - A hoặc B.

Để khởi động bộ định thời, bạn cần đặt các bit tương ứng trong thanh ghi điều khiển bộ định thời TCCRn (Thanh ghi điều khiển bộ đếm thời gian/bộ đếm), sau đó nó ngay lập tức bắt đầu công việc của mình.

Chúng ta sẽ chỉ xem xét một số chế độ hoạt động của bộ hẹn giờ. Nếu bạn cần làm việc ở một chế độ khác, hãy đọc Bảng dữ liệu cho ATMega16 - mọi thứ đều được viết ở đó rất chi tiết bằng tiếng Anh, thậm chí còn đưa ra các ví dụ về chương trình bằng C và trình biên dịch mã (không phải vô ích khi nó chiếm tới 357 trang in chữ!).

Bây giờ chúng ta hãy chuyển sang các nút.

Nếu chúng ta định sử dụng một số lượng nhỏ nút (tối đa 9 nút), thì chúng phải được kết nối giữa mặt đất và các chân của bất kỳ cổng vi điều khiển nào. Trong trường hợp này, bạn nên tạo các đầu vào chân này bằng cách đặt các bit tương ứng trong thanh ghi DDRx và bật điện trở kéo lên bên trong bằng cách đặt các bit trong thanh ghi PORTx. Trong trường hợp này, điện áp trên các “chân” này sẽ là 5 V. Khi nhấn nút, đầu vào MK sẽ đóng về GND và điện áp trên nó giảm xuống 0 (hoặc có thể ngược lại - đầu ra MK được nối đất ở trạng thái chán nản). Điều này thay đổi thanh ghi PINx, lưu trữ trạng thái hiện tại của cổng (không giống như PORTx, đặt trạng thái của cổng khi không có tải, tức là trước khi nhấn bất kỳ nút nào). Bằng cách đọc định kỳ trạng thái PINx, bạn có thể xác định rằng một nút đã được nhấn.

CHÚ Ý! Nếu bit tương ứng trong thanh ghi DDRx được đặt thành 1 cho nút của bạn, thì việc nhấn mạnh nút này có thể dẫn đến hiệu ứng pháo hoa nhỏ - xuất hiện khói xung quanh MK. Đương nhiên sau chuyện này MK sẽ phải bị ném vào sọt rác…

Hãy chuyển sang phần thực tế. Tạo một không gian làm việc mới và một dự án mới trong IAR với tên như TimeButton. Đặt các tùy chọn dự án như được mô tả trong bài viết trước. Bây giờ hãy gõ đoạn mã nhỏ sau đây.

#bao gồm"iom16.h" trống rỗng init_timer0( trống rỗng) //Khởi tạo bộ đếm thời gian/bộ đếm0( OCR0 = 255; //Nội dung của thanh ghi so sánh //Đặt chế độ hoạt động của bộ hẹn giờ TCCR0 = (1 void init_timer2( trống rỗng) //Khởi tạo bộ đếm thời gian/bộ đếm2( OCR2 = 255; TCCR2 = (1 //Đặt ngắt khớp cho nó) trống rỗng chủ yếu( trống rỗng) (DDRB = 255; init_timer0(); init_timer2(); trong khi(1) { } } #pragma vectơ = TIMER2_COMP_vect // Ngắt hẹn giờ 2 __ ngắt khoảng trống nhấp nháy() ( nếu như((PORTB & 3) == 1) ( PORTB &= (0xFF // Vô hiệu hóa các chân PB0, PB1 PORTB |= 2; // Kích hoạt PB1 } khác( PORTB &= (0xFF // Vô hiệu hóa các chân PB0, PB1 PORTB |= 1; // Kích hoạt PB0 } }

Hãy xem nó hoạt động như thế nào. Các hàm init_timern thiết lập các bit trong các thanh ghi TCCRn, OCRn và TIMSK và phương pháp này có vẻ lạ hoặc xa lạ đối với một số người. Trước tiên chúng tôi sẽ phải giải thích mục nhập đó là gì “(1

trong đó a là số có biểu diễn nhị phân cần được dịch chuyển và b cho biết số đó cần được dịch chuyển bao nhiêu bit. Trong trường hợp này, có thể mất giá trị được lưu trong a (nghĩa là không phải lúc nào cũng có thể khôi phục từ C những gì có trong a). Hãy xem một ví dụ:

Điều gì sẽ kết thúc trong C sau khi thực hiện dòng C = (22

2 trong mã nhị phân sẽ trông giống như 00010110 và sau khi dịch sang trái 3 bit, chúng ta nhận được C = 10110000.

Tương tự, có sự dịch chuyển sang phải. Một vi dụ khac:

ký tự C; … C = ((0xFF > 2);

Đầu tiên, hành động trong dấu ngoặc bên trong sẽ được thực hiện (0xFF là 255 trong mã thập lục phân), từ 11111111 kết quả sẽ là 11111100, sau đó sẽ xảy ra sự dịch chuyển sang phải và chúng ta sẽ nhận được C = 00111111. Như chúng ta thấy, ở đây có hai các phép toán nghịch đảo lẫn nhau dẫn đến một số khác, vì chúng ta đã mất hai bit. Điều này sẽ không xảy ra nếu biến C có kiểu int, vì int chiếm 16 bit.

Bây giờ chúng ta hãy xem xét thêm hai toán tử bit được sử dụng rộng rãi trong lập trình MK. Đây là các toán tử bitwise và (&) và bitwise hoặc (|). Tôi nghĩ chúng hoạt động như thế nào sẽ rõ ràng qua các ví dụ:

Hành động: Kết quả (ở dạng nhị phân): C = 0; // C = 00000000 C = (1 // C = 00100101 C |= (1 // C = 00101101 C &= (0xF0 >> 2); // C = 00101100 C = (C & 4) | 3; // C = 00000111

Tôi gần như quên mất! Ngoài ra còn có "độc quyền bitwise hoặc" (^). Nó so sánh các bit tương ứng trong một số và nếu chúng giống nhau thì trả về 0, nếu không thì trả về một.

Hãy quay trở lại chương trình của chúng tôi. Nó nói "(1

/* Thanh ghi điều khiển bộ định thời/bộ đếm 0 */ #định nghĩa FOC0 7 #định nghĩa WGM00 6 #định nghĩa COM01 5 #định nghĩa COM00 4 #định nghĩa WGM01 3 #định nghĩa CS02 2 #định nghĩa CS01 1 #định nghĩa CS00 0

Khi biên dịch chương trình, mục WGM01 chỉ được thay thế bằng số 3 và kết quả là một mục nhập chính xác. WGM01 được gọi là macro và không giống như một biến, nó không chiếm dung lượng trong bộ nhớ (ngoại trừ trong bộ nhớ của lập trình viên :-).

Bây giờ nếu nhìn vào Datasheet sẽ dễ dàng nhận thấy WGM01 chính là tên của bit thứ 3 trong thanh ghi TCCR0. Điều tương tự cũng áp dụng cho các bit còn lại của thanh ghi này. Sự trùng hợp này không phải ngẫu nhiên mà áp dụng cho tất cả các sổ đăng ký MK (hoặc gần như tất cả). Tức là bằng cách viết “(1

Tổng cộng, dòng

nghĩa là chế độ CTC được bật, khi clock0 được kích hoạt, trạng thái của “chân” OS0 (hay còn gọi là PB3) thay đổi, nội dung của bộ đếm tăng sau mỗi 1024 chu kỳ xung nhịp.

Tương tự với bộ định thời2: TCCR2 = (1

Thanh ghi TIMSK (Thanh ghi MaSK ngắt bộ đếm thời gian/bộ đếm) thiết lập chế độ ngắt. Chúng tôi đã viết

có nghĩa là làm gián đoạn bộ đếm thời gian2 khi TCNT2 và OCR2 khớp nhau. Chức năng cuối cùng là chức năng Ngắt khớp hẹn giờ thực tế. Các ngắt được khai báo như sau:

#véc tơ thực dụng= Vectơ __ngắt TÊN LOẠI()

trong đó VECTOR là macro vectơ ngắt (có nghĩa đơn giản là một số đặc trưng cho ngắt này); Các macro này được liệt kê theo thứ tự ưu tiên giảm dần trong tệp iom16.h. TYPE là loại giá trị được hàm trả về, trong trường hợp của chúng tôi là void (không có gì). TÊN – tên tùy chỉnh cho chức năng này. Với sự gián đoạn, chúng tôi vẫn sẽ có thời gian để làm việc đó trong tương lai.

Khi thực hiện chức năng của chúng ta, các đèn LED kết nối với PB0 và PB1 sẽ lần lượt nhấp nháy. Rõ ràng, tần số là 11059200/(256*1024) = 42 Hz. Nó nhanh chóng, nhưng sẽ được chú ý bằng mắt thường. Nhân tiện, việc sử dụng bộ hẹn giờ giúp bạn có thể đếm các khoảng thời gian chính xác không phụ thuộc vào độ phức tạp của chương trình và thứ tự thực hiện chương trình (nhưng nếu bạn không có nhiều hơn một lần ngắt).

Vì vậy, hãy lưu tệp dưới dạng “TimerDebug.c”, thêm nó vào dự án, biên dịch nó, flash MK. Chúng ta thấy gì? Đèn LED nối với chân PB3 sẽ nhấp nháy tích cực nhưng sẽ không có thay đổi nào ở PB0 và PB1. Có chuyện gì vậy? Có điều gì đó thực sự không ổn?

Để tìm hiểu, chúng tôi sẽ phải gỡ lỗi chương trình của chúng tôi. Vì IAR không có Trình gỡ lỗi nên bạn sẽ phải sử dụng AVR Studio. Môi trường phát triển này có thể được tải xuống từ trang web của nhà sản xuất http://atmel.com. Tôi không nghĩ sẽ có bất kỳ vấn đề nào với việc cài đặt nó. Trước khi khởi động AVR Studio, hãy chọn chế độ Gỡ lỗi trong IAR và tạo tệp cof gỡ lỗi (tất cả các tùy chọn dự án phải được đặt như mô tả trong bài viết trước).

Sau khi mở AVR Studio, chúng ta sẽ thấy một cửa sổ chào mừng, trong đó chúng ta sẽ chọn “Mở”. Bây giờ chúng ta đi vào thư mục chứa dự án, trong Debug\Exe, chọn “TimerDebug.cof” ở đó, tạo một dự án mà họ đề xuất, chọn thiết bị ATMega16 và chế độ gỡ lỗi Trình mô phỏng. Sau đó, nếu mọi thứ được thực hiện chính xác, quá trình gỡ lỗi sẽ ngay lập tức bắt đầu.

Môi trường gỡ lỗi ở đây rất thuận tiện, bởi vì... cho phép bạn xem nội dung của tất cả các thanh ghi MK, cũng như đặt giá trị thủ công cho chúng bằng những cú click chuột. Ví dụ: nếu bạn đặt cờ ngắt trong thanh ghi TIFR ở bit 7 (dưới ô vuông màu đen trong TIMSK), thì bước tiếp theo của chương trình (nhấn F10 hoặc F11) sẽ là xử lý ngắt (cờ sẽ được đặt tự động khi các thanh ghi TCNT2 và OCR2 khớp nhau). Nhưng thật ngạc nhiên, sẽ không có sự gián đoạn nào cả!

Câu hỏi đặt ra: tại sao?

Hãy mở thanh ghi CPU, SREG. Thanh ghi này xác định hoạt động của bộ xử lý và cụ thể là bit thứ bảy của nó (I-bit, Bit ngắt) chịu trách nhiệm xử lý tất cả các ngắt trong MK. Chúng tôi chưa cài đặt nó. Ngay khi bạn đặt nó, ngắt sẽ ngay lập tức được thực thi (nếu bit thứ bảy trong TIFR được đặt cùng lúc).

Bạn có thể nhận thấy một tính năng thú vị: ngay khi bộ xử lý bắt đầu xử lý ngắt, bit này (cờ cho phép xử lý ngắt) sẽ bị xóa và khi thoát khỏi chức năng ngắt, nó sẽ tự động được đặt lại. Điều này không cho phép bộ xử lý, nếu không thực hiện một ngắt, sẽ lấy một ngắt khác (xét cho cùng, nó điều hướng chương trình theo cách chính xác - bằng cờ).

Điều này có nghĩa là bạn cần thêm một dòng mã để đặt bit này thành một. Chúng tôi sẽ thêm nó vào hàm init_timer2. Bạn sẽ nhận được những điều sau:

trống rỗng init_timer2( trống rỗng) ( SREG |= (1 //Đã thêm dòng này OCR2 = 255; TCCR2 = (1

Bây giờ, sau khi chọn cấu hình Phát hành và flash MK bằng cách nhấn F7 và khởi chạy AVReal32.exe, chúng tôi sẽ rất vui khi thấy mọi thứ hoạt động như bình thường.

Bình luận: Khi gỡ lỗi một chương trình, bạn nên giảm khoảng thời gian hẹn giờ nếu chúng quá dài, vì trong quá trình gỡ lỗi trong AVR Studio, chương trình chạy chậm hơn hàng nghìn lần so với bên trong MK và bạn sẽ không phải đợi bộ hẹn giờ kích hoạt. Nói chung, việc gỡ lỗi hoàn toàn tương tự như việc gỡ lỗi trong các hệ thống lập trình khác, chẳng hạn như Visual C++.

Bây giờ, sau khi đã học cách gỡ lỗi chương trình, hãy tạo một tệp mới trong IAR (và lưu tệp cũ và xóa nó khỏi dự án) và nhập mã sau:

#bao gồm"iom16.h" int dài không dấu bộ đếm = 0; //Bộ đếm hình thành các khoảng thời gian ký tự không dấu B0Đã nhấn = 0; // Trạng thái của nút0 được lưu ở đây (0 - không được nhấn, 1 - được nhấn) ký tự không dấu B1Đã ép = 0; // Trạng thái của nút 1 được lưu ở đây (0 - không được nhấn, 1 - được nhấn) //Khởi tạo bộ đếm thời gian2 //Cần tăng bộ đếm sau mỗi 11059 chu kỳ xung nhịp (1 ms). Chúng tôi nhận được nó cứ sau 1,001175 mili giây trống rỗng init_timer2() ( OCR2 = 173; TCCR2 = (1 //Khởi tạo các cổng I/O init_io_ports() ( DDRA =(1//hình thành độ trễ tính bằng Pause_ms mili giây trống rỗng trì hoãn( int dài không dấu Tạm dừng_ms) ( bộ đếm = 0; trong khi(bộ đếm void main() ( SREG |= (1 //Cho phép ngắt init_timer2(); //Bật bộ đếm thời gian2 cứ sau 64 tích tắc, đếm tới 173 init_io_ports(); //Kích hoạt cổng I/O trong khi(1) { // Nút xử lý 0 nếu như(B0Đã nhấn == 1) { // tăng PORTB, chờ giải phóng CỔNG ++; B0Đã nhấn = 0; trong khi((PINC & (1 người khác ( nếu như((PINC & (1 //Sửa lỗi nhấn ( delay(50); nếu như((PINC & (1 // Kiểm tra việc nhấn ( B0Pressed = 1; } } } // Nút xử lý 1 nếu như(B1Đã nhấn == 1) //Nếu nút được bấm, { //giảm PORTB, chờ giải phóng PORTB--; B1Đã ép = 0; trong khi((PINC & (1 người khác ( nếu như((PINC & (1 //Sửa lỗi nhấn ( delay(200); // Loại bỏ hiện tượng "bật phím" nếu như((PINC & (1 // Kiểm tra việc nhấn ( B1Pressed = 1; // Đặt cờ "nhấn nút" } } } } } //Ngắt bởi bộ định thời 2, do đó bộ đếm tăng #véc tơ thực dụng= TIMER2_COMP_vect __ngắt void inc_delay_counter() ( counter++; )

Trước tiên, tôi khuyên bạn nên lấy một tệp chương trình cơ sở làm sẵn (các tệp cho bài viết, thư mục Phát hành, tệp TimeButton.hex hoặc biên dịch văn bản này) và ghi nó vào MK. Sau đó tháo cáp chương trình cơ sở, kết nối các nút với PC0 và PC1 và thử nhấn chúng. Chúng ta sẽ thấy rằng khi bạn nhấn một trong các nút, thanh ghi PORTB sẽ tăng (đèn LED sáng lên) và khi bạn nhấn nút còn lại, nó sẽ giảm. Nếu nó không hoạt động, hãy thử nhấn một nút trong khi giữ nút kia - nó sẽ hoạt động. Thực tế là tôi đã kết nối các nút theo cách sau: khi bạn nhấn nút, đầu ra MK “lơ lửng” trong không khí và khi thả ra, nó sẽ chạm đất. Nếu bạn kết nối các nút theo cách khác, bạn sẽ chỉ phải hiện đại hóa chương trình một chút.

Hãy nhìn vào mã. Ở đây, làm việc với bộ đếm thời gian được tổ chức hơi khác một chút. Nó kích hoạt cứ sau 11072 chu kỳ xung nhịp (nghĩa là cứ sau 1,001175 ms) và tăng biến bộ đếm. Ngoài ra còn có một hàm delay(long unsigned int Pause_ms), lấy số mili giây Pause_ms làm tham số, đặt lại bộ đếm và đợi bộ đếm đạt giá trị Pause_ms, sau đó MK tiếp tục hoạt động. Do đó, bằng cách viết delay(1500), chúng ta sẽ tạo ra độ trễ trong chương trình là 1,5 giây. Điều này rất thuận tiện cho việc hình thành các khoảng thời gian.

Mọi thứ dường như rõ ràng với bộ đếm thời gian. Nhưng nó được dùng để làm gì? Hãy xem xét vòng lặp vô hạn while(1) trong main(). Vòng lặp này kiểm tra trạng thái của các nút bằng cách phân tích nội dung của thanh ghi PINB. Tại sao có độ trễ 50 ms ở đó? Đây là sự loại bỏ cái gọi là. "cuộc trò chuyện chính" Thực tế là khi bạn nhấn nút, một tiếp điểm sẽ chạm vào một tiếp điểm khác và vì các tiếp điểm là kim loại nên tác động này có tính đàn hồi. Các điểm tiếp xúc, lò xo, đóng và mở nhiều lần, mặc dù thực tế là ngón tay chỉ ấn một lần. Điều này dẫn đến việc MK ghi lại một số lần nhấp chuột. Chúng ta hãy nhìn vào biểu đồ điện áp ở đầu ra PC0 theo thời gian. Nó có thể trông như thế này:

Điểm A là thời điểm nút được nhấn. MK có thể sửa được. Sau đó, có một số hiện tượng đoản mạch và hở mạch (có thể không có hoặc có thể có 12 mạch trong số đó - hiện tượng này có thể được coi là ngẫu nhiên). Tại điểm B, tiếp điểm đã được cố định chắc chắn. Giữa A và B có trung bình khoảng 10 ms. Cuối cùng, tại điểm D, một sự mở xảy ra. Làm thế nào để thoát khỏi hiện tượng khó chịu này? Hóa ra là rất đơn giản. Cần ghi lại thời điểm nhấn nút (điểm A), sau một khoảng thời gian, ví dụ 50 ms (điểm C), kiểm tra xem nút đã thực sự được nhấn chưa, thực hiện thao tác tương ứng với nút này và chờ đợi thời điểm đó. nó được phát hành (điểm D). Nghĩa là, bạn cần tạm dừng từ A đến C, sao cho tất cả “chớp mắt” đều nằm trong khoảng dừng này. Bây giờ hãy thử loại bỏ dòng tạo ra độ trễ, biên dịch chương trình và ghép nó vào MK. Chỉ cần nhấn các nút, bạn có thể dễ dàng đảm bảo rằng tất cả “sự dày vò” này không vô ích.

Nhưng bạn nên làm gì nếu cần kết nối 40 nút với MK? Rốt cuộc, nó chỉ có 32 chân. Có vẻ như không có cách nào. Nó thực sự có thể. Trong trường hợp này, một thuật toán gọi là gating được sử dụng. Để thực hiện việc này, bạn cần kết nối các nút ở dạng ma trận, như trong hình (hình này được lấy từ cuốn sách “MK AVR, một khóa học giới thiệu” của Morton, trong đó viết về lập trình AVR trong trình biên dịch chương trình).

Khi áp dụng cho nhật ký PB0 đầu ra. 1 (+5V) và ghi vào nhật ký PB1 và ​​PB2. 0 cho phép xử lý các nút 1, 4 và 7. Sau đó, có thể tìm ra trạng thái của từng nút bằng cách kiểm tra điện áp ở một trong các chân PB3..PB5. Do đó, áp dụng tuần tự cho các chân ghi nhật ký PB0..PB2. 1, trạng thái của tất cả các nút có thể được xác định. Rõ ràng rằng các chân PB0..PB2 phải là đầu ra và PB0..PB2 là đầu vào. Để xác định cần bao nhiêu chân cho một mảng nút X, bạn cần tìm một cặp thừa số X có tổng nhỏ nhất (đối với trường hợp của chúng ta có 40 nút, đây sẽ là số 5 và 8). Điều này có nghĩa là có thể kết nối tối đa 256 nút với một MK (và thậm chí nhiều hơn khi sử dụng bộ giải mã, nhưng sau này sẽ có nhiều hơn trên bộ giải mã). Tốt hơn là tạo ra ít chân đầu ra hơn và nhiều chân đầu vào hơn. Trong trường hợp này, việc quét tất cả các hàng của ma trận sẽ mất ít thời gian hơn. Phương thức kết nối này (nhấp nháy) không chỉ có ở các nút. Ở đó bạn có thể kết nối nhiều loại thiết bị, từ ma trận LED đến chip nhớ flash.

© Kiselev La Mã
tháng 6 năm 2007


Bộ định thời và bộ đếm cho vi điều khiển AVR (đồng hồ thời gian thực). AVR Bài 7

Khi tôi mới bắt đầu nghiên cứu về vi điều khiển, tôi đã muốn làm như vậy. Thành thật mà nói, tôi chỉ muốn thử bật TV từ 7 đến 8 giờ, còn thời gian còn lại lẽ ra nên tắt TV đi. Tôi đã tạo ra thiết bị này nhưng chưa bao giờ sử dụng nó...

Tất cả các bộ vi điều khiển AVR đều có một số bộ hẹn giờ tích hợp. Chúng cũng có thể được chia thành bộ hẹn giờ đa năng và bộ hẹn giờ giám sát, được thiết kế để khởi động lại MK khi nó bị treo.

Bộ hẹn giờ mục đích chung có thể:

  • Đồng hồ từ đồng hồ thạch anh bên ngoài ở tần số 32768 hertz
  • Đếm các khoảng thời gian khác nhau
  • Đếm xung ngoài ở chế độ đếm
  • Tạo tín hiệuPWM ở một số chân MK nhất định
  • Tạo các ngắt cho một số sự kiện, ví dụ như khi có sự cố tràn

Bộ đếm thời gian có thể được bấm giờ từ bộ tạo xung nhịp bên trong và từ đầu vào đếm. Chúng ta hãy xem chức năng của bộ đếm thời gian 1 trong vi điều khiển atmega8. Khởi chạy CodeVision AVR, tạo một dự án mới và đồng ý với đề nghị khởi chạy Code WizardAVR

Hãy sử dụng bộ đếm thời gian2 làm ví dụ để triển khai đồng hồ thời gian thực với đầu ra là màn hình LCD; để làm được điều này, chúng tôi đặt bộ hẹn giờ như trong ảnh chụp màn hình

ở đây chúng ta đặt nguồn xung nhịp bên ngoài cho bộ hẹn giờ, làm nguồn bên ngoài chúng ta sẽ sử dụng thạch anh đồng hồ ở tần số 32768 hertz, sau đó chúng ta sẽ đặt bộ đếm gộp trước thành 128, tức là bộ hẹn giờ sẽ hoạt động ở tần số 32768/128=256 , và thanh ghi đếm là 8 bit (số tối đa là 255), tính ra nó sẽ tràn một lần trong một giây, sau đó chúng ta đánh dấu vào ô bên cạnh ngắt tràn và bấm vào tập tin-> Tạo, lưu và thoát.

Trình hướng dẫn mã đã tạo mã sau:

#bao gồm // Thói quen dịch vụ ngắt tràn bộ đếm thời gian 2 ngắt void time2_ovf_isr(void) ( ) void main(void) ( // Khởi tạo cổng đầu vào/đầu ra // Khởi tạo cổng B PORTB=0x00; DDRB=0x00; // Khởi tạo cổng C PORTC=0x00; DDRC=0x00; // Khởi tạo cổng D PORTD=0x00; DDRD=0x00; // Khởi tạo bộ định thời/bộ đếm 0 // Nguồn đồng hồ: Đồng hồ hệ thống // Giá trị đồng hồ: Bộ định thời 0 Đã dừng TCCR0=0x00; TCNT0=0x00; // Bộ định thời /Khởi tạo bộ đếm 1 // Nguồn đồng hồ: Đồng hồ hệ thống // Giá trị đồng hồ: 125.000 kHz // Chế độ: Top điều khiển tốc độ cao nhất = 00FFh // Đầu ra OC1A: Discon. // Đầu ra OC1B: Discon. // Bộ khử tiếng ồn: Tắt // Đầu vào Chụp trên Falling Edge // Ngắt tràn bộ đếm thời gian1: Tắt // Ngắt chụp đầu vào: Tắt // Ngắt so sánh một kết quả khớp: Tắt // So sánh ngắt kết hợp B: Tắt TCCR1A=0x01; TCCR1B=0x0A; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Khởi tạo bộ đếm thời gian/bộ đếm 2 // Nguồn đồng hồ: chân TOSC1 // Giá trị đồng hồ: PCK2/128 // Chế độ: Top bình thường=FFh // Đầu ra OC2: Ngắt kết nối ASSR=0x08; TCCR2=0x05; TCNT2=0x00; OCR2=0x00; // Khởi tạo (các) ngắt bên ngoài // INT0: Tắt // INT1: Tắt MCUCR=0x00; // (Các) bộ định thời/Bộ đếm Khởi tạo (các) ngắt TIMSK=0x40; // Khởi tạo bộ so sánh tương tự // Bộ so sánh tương tự: Tắt // Ghi đầu vào bộ so sánh tương tự bằng bộ đếm thời gian/bộ đếm 1: Tắt ACSR=0x80; SFIOR=0x00; // Ngắt kích hoạt toàn cục #asm("sei") while (1) ( ); )

#bao gồm #bao gồm // Chức năng của mô-đun LCD chữ và số #asm .equ __lcd_port=0x12 ;PORTD #endasm #include ký tự không dấu thứ hai=0; //biến lưu trữ giây unsigned char phút=0; //biến lưu trữ số phút unsigned char giờ=0; //biến lưu trữ giờ char lcd_buffer; // bộ đệm biến cho đầu ra hiển thị // Thói quen dịch vụ ngắt tràn Hẹn giờ 2 ngắt void time2_ovf_isr(void) ( if (++second==59) // tăng số giây lên 1 và kiểm tra đẳng thức 59 (giây = 0; if (+ +phút==59) (phút = 0; if (++giờ==59) ( giờ = 0; ) ) lcd_clear(); // xóa màn hình trước khi hiển thị lcd_gotoxy(0,0); // di chuyển con trỏ tới điểm x=0 y=0 sprintf(lcd_buffer,"%i:%i:%i",hour,phút,giây); // tạo một dòng cho đầu ra lcd_puts(lcd_buffer); // hiển thị dòng trên màn hình ) void main( void) ( // Khởi tạo bộ đếm thời gian/bộ đếm 2 // Nguồn đồng hồ: chân TOSC1 // Giá trị đồng hồ: PCK2/128 // Chế độ: Top bình thường=FFh // Đầu ra OC2: Đã ngắt kết nối ASSR=0x08; TCCR2 =0x05; TCNT2=0x00 ; OCR2=0x00; // (Các) bộ định thời/Bộ đếm Khởi tạo ngắt TIMSK=0x40; // Khởi tạo mô-đun LCD lcd_init(16); // Ngắt cho phép toàn cầu #asm(" sei") trong khi (1 ) ( ); )

Chương trình đã sẵn sàng, bây giờ chúng ta hãy tạo sơ đồ trong Proteus