Viết trình xử lý ngắt¶
Trên phần cứng phù hợp, MicroPython cung cấp khả năng viết trình xử lý ngắt bằng Python. Trình xử lý ngắt - còn được gọi là trình phục vụ ngắt (ISR) - được định nghĩa dưới dạng hàm gọi lại. Chúng được thực thi để đáp lại một sự kiện như kích hoạt bộ định thời hay thay đổi điện áp trên một chân (pin). Các sự kiện như vậy có thể xảy ra tại bất kỳ thời điểm nào trong quá trình thực thi mã chương trình. Điều này mang lại những hệ quả đáng kể, một số đặc thù cho ngôn ngữ MicroPython, một số khác chung cho tất cả các hệ thống có khả năng đáp ứng sự kiện thời gian thực. Tài liệu này trình bày các vấn đề đặc thù của ngôn ngữ trước, sau đó là phần giới thiệu ngắn gọn về lập trình thời gian thực dành cho những ai chưa quen.
Phần giới thiệu này sử dụng các thuật ngữ mơ hồ như "chậm" hay "nhanh nhất có thể". Đây là chủ ý, vì tốc độ phụ thuộc vào từng ứng dụng. Thời gian chấp nhận được cho một ISR phụ thuộc vào tần suất xảy ra ngắt, bản chất của chương trình chính và sự hiện diện của các sự kiện đồng thời khác.
Mẹo và các thực hành được khuyến nghị¶
Phần này tóm tắt các điểm được trình bày chi tiết bên dưới và liệt kê các khuyến nghị chính cho mã trình xử lý ngắt.
Giữ mã càng ngắn gọn và đơn giản càng tốt.
Tránh cấp phát bộ nhớ: không thêm phần tử vào danh sách hay chèn vào từ điển, không dùng số thực dấu phẩy động.
Hãy xem xét dùng
micropython.scheduleđể vượt qua hạn chế trên.Khi một ISR trả về nhiều byte, hãy dùng
bytearrayđược cấp phát trước. Nếu cần chia sẻ nhiều số nguyên giữa ISR và chương trình chính, hãy xem xét dùng mảng (array.array).Khi dữ liệu được chia sẻ giữa chương trình chính và ISR, hãy xem xét tắt ngắt trước khi truy cập dữ liệu trong chương trình chính và bật lại ngay sau đó (xem Vùng Tới Hạn).
Cấp phát bộ đệm ngoại lệ khẩn cấp (xem bên dưới).
Các vấn đề MicroPython¶
Bộ đệm ngoại lệ khẩn cấp¶
Nếu xảy ra lỗi trong một ISR, MicroPython không thể tạo báo cáo lỗi trừ khi một bộ đệm đặc biệt được tạo ra cho mục đích đó. Việc gỡ lỗi sẽ đơn giản hơn nếu đoạn mã sau được đưa vào bất kỳ chương trình nào sử dụng ngắt.
import micropython
micropython.alloc_emergency_exception_buf(100)
Bộ đệm ngoại lệ khẩn cấp chỉ có thể lưu một dấu vết ngăn xếp ngoại lệ. Điều này có nghĩa là nếu một ngoại lệ thứ hai được ném ra trong quá trình xử lý ngoại lệ trong khi heap bị khóa, dấu vết ngăn xếp của ngoại lệ thứ hai sẽ thay thế dấu vết ban đầu - ngay cả khi ngoại lệ thứ hai được xử lý sạch sẽ. Điều này có thể dẫn đến các thông báo ngoại lệ gây nhầm lẫn nếu bộ đệm được in ra sau đó.
Sự đơn giản¶
Vì nhiều lý do, điều quan trọng là giữ cho mã ISR càng ngắn gọn và đơn giản càng tốt. Nó chỉ nên thực hiện những gì cần làm ngay lập tức sau sự kiện đã gây ra nó: các thao tác có thể hoãn lại nên được ủy thác cho vòng lặp chương trình chính. Thông thường, một ISR sẽ xử lý thiết bị phần cứng đã gây ra ngắt, chuẩn bị cho ngắt tiếp theo xảy ra. Nó sẽ giao tiếp với vòng lặp chính bằng cách cập nhật dữ liệu được chia sẻ để chỉ ra rằng ngắt đã xảy ra, và sau đó trả về. Một ISR nên trả lại quyền điều khiển cho vòng lặp chính càng nhanh càng tốt. Đây không phải vấn đề đặc thù của MicroPython nên được đề cập chi tiết hơn bên dưới.
Giao tiếp giữa ISR và chương trình chính¶
Thông thường, một ISR cần giao tiếp với chương trình chính. Phương tiện đơn giản nhất để làm điều này là thông qua một hoặc nhiều đối tượng dữ liệu được chia sẻ, được khai báo là biến toàn cục hoặc được chia sẻ thông qua một lớp (xem bên dưới). Có nhiều hạn chế và nguy hiểm xung quanh việc thực hiện điều này, được đề cập chi tiết hơn bên dưới. Các đối tượng kiểu số nguyên, bytes và bytearray thường được sử dụng cho mục đích này cùng với mảng (từ module array) có thể lưu trữ nhiều kiểu dữ liệu khác nhau.
Sử dụng phương thức đối tượng làm hàm gọi lại¶
MicroPython hỗ trợ kỹ thuật mạnh mẽ này cho phép ISR chia sẻ biến thực thể với mã cơ bản. Nó cũng cho phép một lớp triển khai trình điều khiển thiết bị hỗ trợ nhiều thực thể thiết bị. Ví dụ sau đây khiến hai đèn LED nhấp nháy ở các tốc độ khác nhau.
import machine
import micropython
micropython.alloc_emergency_exception_buf(100)
class Foo(object):
def __init__(self, freq, led):
self.led = led
self.timer = machine.Timer(-1, freq=freq, callback=self.cb, hard=True)
def cb(self, tim):
self.led.toggle()
red = Foo(1, machine.LED("LED_RED"))
green = Foo(0.8, machine.LED("LED_GREEN"))
Trong ví dụ này, thực thể red điều khiển đèn LED đỏ từ bộ định thời ảo 1 Hz: mỗi khi bộ định thời kích hoạt, red.cb() được gọi, bật tắt đèn LED đỏ. Thực thể green hoạt động tương tự với bộ định thời 0.8 Hz bật tắt đèn LED xanh lá. Việc sử dụng phương thức thực thể mang lại hai lợi ích. Thứ nhất, một lớp duy nhất cho phép mã được chia sẻ giữa nhiều thực thể phần cứng. Thứ hai, là một phương thức bị ràng buộc, đối số đầu tiên của hàm gọi lại là self. Điều này cho phép hàm gọi lại truy cập dữ liệu thực thể và lưu trạng thái giữa các lần gọi liên tiếp. Ví dụ, nếu lớp trên có biến self.count được đặt về không trong hàm khởi tạo, cb() có thể tăng biến đếm. Khi đó các thực thể red và green sẽ duy trì các đếm độc lập về số lần mỗi đèn LED thay đổi trạng thái.
Tạo đối tượng Python¶
ISR không thể tạo các thực thể đối tượng Python. Điều này là do MicroPython cần cấp phát bộ nhớ cho đối tượng từ một kho các khối bộ nhớ trống gọi là heap. Điều này không được phép trong trình xử lý ngắt vì cấp phát heap không phải là tái nhập. Nói cách khác, ngắt có thể xảy ra khi chương trình chính đang thực hiện cấp phát một phần - để duy trì tính toàn vẹn của heap, trình thông dịch không cho phép cấp phát bộ nhớ trong mã ISR.
Hệ quả của điều này là ISR không thể sử dụng số học dấu phẩy động; điều này là vì số thực là đối tượng Python. Tương tự, một ISR không thể thêm phần tử vào danh sách. Trong thực tế, có thể khó xác định chính xác những cấu trúc mã nào sẽ cố gắng thực hiện cấp phát bộ nhớ và gây ra thông báo lỗi: đây là một lý do nữa để giữ mã ISR ngắn gọn và đơn giản.
Một cách để tránh vấn đề này là ISR sử dụng các bộ đệm được cấp phát trước. Ví dụ, hàm khởi tạo lớp tạo một thực thể bytearray và một cờ boolean. Phương thức ISR gán dữ liệu vào các vị trí trong bộ đệm và đặt cờ. Việc cấp phát bộ nhớ xảy ra trong mã chương trình chính khi đối tượng được khởi tạo, không phải trong ISR.
Các phương thức I/O của thư viện MicroPython thường cung cấp tùy chọn sử dụng bộ đệm được cấp phát trước. Ví dụ, machine.I2C.readfrom_into() đọc vào bộ đệm có thể thay đổi do người gọi cung cấp: điều này cho phép sử dụng nó trong ISR.
Một cách tạo đối tượng mà không dùng lớp hay biến toàn cục là như sau:
def set_volume(t, buf=bytearray(3)):
buf[0] = 0xa5
buf[1] = t >> 4
buf[2] = 0x5a
return buf
Trình biên dịch khởi tạo đối số buf mặc định khi hàm được nạp lần đầu tiên (thường khi module chứa nó được import).
Một trường hợp tạo đối tượng xảy ra khi tham chiếu đến một phương thức bị ràng buộc được tạo ra. Điều này có nghĩa là một ISR không thể truyền một phương thức bị ràng buộc cho một hàm. Một giải pháp là tạo tham chiếu đến phương thức bị ràng buộc trong hàm khởi tạo lớp và truyền tham chiếu đó trong ISR. Ví dụ:
class Foo():
def __init__(self):
self.bar_ref = self.bar # Allocation occurs here
self.x = 0.1
self.tim = machine.Timer(-1, freq=2, callback=self.cb, hard=True)
def bar(self, _):
self.x *= 1.2
print(self.x)
def cb(self, t):
# Passing self.bar would cause allocation.
micropython.schedule(self.bar_ref, 0)
Các kỹ thuật khác là định nghĩa và khởi tạo phương thức trong hàm khởi tạo hoặc truyền Foo.bar() với đối số self.
Sử dụng đối tượng Python¶
Một hạn chế thêm về đối tượng xuất phát từ cách Python hoạt động. Khi một câu lệnh import được thực thi, mã Python được biên dịch thành bytecode, với một dòng mã thường ánh xạ tới nhiều bytecode. Khi mã chạy, trình thông dịch đọc từng bytecode và thực thi nó như một loạt các lệnh mã máy. Vì một ngắt có thể xảy ra bất cứ lúc nào giữa các lệnh mã máy, dòng mã Python gốc có thể chỉ được thực thi một phần. Do đó, một đối tượng Python như set, list hay dictionary được sửa đổi trong vòng lặp chính có thể thiếu tính nhất quán nội bộ vào thời điểm ngắt xảy ra.
Một kết quả điển hình như sau. Trong các trường hợp hiếm gặp, ISR sẽ chạy vào chính xác thời điểm khi đối tượng đang được cập nhật một phần. Khi ISR cố gắng đọc đối tượng, kết quả là sự cố. Vì những vấn đề như vậy thường xảy ra vào những dịp hiếm hoi, ngẫu nhiên, chúng có thể khó chẩn đoán. Có những cách để giải quyết vấn đề này, được mô tả trong Vùng Tới Hạn bên dưới.
Điều quan trọng là phải hiểu rõ điều gì cấu thành việc sửa đổi một đối tượng. Thay đổi nội dung của một mảng hay bytearray là an toàn. Điều này vì các byte hay từ được ghi dưới dạng một lệnh mã máy duy nhất không bị gián đoạn: theo thuật ngữ lập trình thời gian thực, việc ghi là nguyên tử. Điều tương tự cũng đúng với việc cập nhật một mục từ điển vì các mục là các từ máy, là số nguyên hay con trỏ đến đối tượng. Một đối tượng do người dùng định nghĩa có thể khởi tạo một mảng hay bytearray. Cả vòng lặp chính và ISR đều hợp lệ khi thay đổi nội dung của chúng.
Nguy hiểm phát sinh khi cấu trúc của đối tượng bị thay đổi, đặc biệt trong trường hợp từ điển. Thêm hoặc xóa khóa có thể kích hoạt quá trình băm lại. Nếu một ISR cứng chạy trong khi quá trình băm lại đang diễn ra và cố gắng truy cập một mục, có thể xảy ra sự cố. Nội bộ, các biến toàn cục được triển khai dưới dạng từ điển. Do đó, chương trình chính nên tạo tất cả các biến toàn cục cần thiết trước khi bắt đầu một tiến trình tạo ra các ngắt cứng. Mã ứng dụng cũng nên tránh xóa các biến toàn cục.
MicroPython hỗ trợ số nguyên có độ chính xác tùy ý. Các giá trị trong khoảng từ 230 -1 đến -230 sẽ được lưu trữ trong một từ máy đơn. Các giá trị lớn hơn được lưu trữ dưới dạng đối tượng Python. Do đó, các thay đổi đối với số nguyên dài không thể được coi là nguyên tử. Việc sử dụng số nguyên dài trong ISR là không an toàn vì có thể có cố gắng cấp phát bộ nhớ khi giá trị biến thay đổi.
Khắc phục giới hạn về số thực dấu phẩy động¶
Nhìn chung, tốt nhất là tránh sử dụng số thực dấu phẩy động trong mã ISR: các thiết bị phần cứng thường xử lý số nguyên và việc chuyển đổi sang số thực thường được thực hiện trong vòng lặp chính. Tuy nhiên, có một số thuật toán DSP yêu cầu tính toán dấu phẩy động. Trên các nền tảng có phần cứng dấu phẩy động (như các OpenMV Cam dựa trên STM32), trình hợp dịch inline ARM Thumb có thể được sử dụng để giải quyết giới hạn này. Điều này là vì bộ xử lý lưu trữ các giá trị số thực trong một từ máy; các giá trị có thể được chia sẻ giữa ISR và mã chương trình chính thông qua một mảng số thực.
Sử dụng micropython.schedule¶
Hàm này cho phép ISR lên lịch một hàm gọi lại để thực thi "rất sớm". Hàm gọi lại được xếp vào hàng đợi để thực thi vào thời điểm khi heap không bị khóa. Do đó, nó có thể tạo đối tượng Python và sử dụng số thực dấu phẩy động. Hàm gọi lại cũng được đảm bảo chạy vào thời điểm khi chương trình chính đã hoàn thành bất kỳ cập nhật nào đối với đối tượng Python, vì vậy hàm gọi lại sẽ không gặp các đối tượng được cập nhật một phần.
Cách sử dụng điển hình là xử lý phần cứng cảm biến. ISR thu thập dữ liệu từ phần cứng và cho phép nó phát ra một ngắt tiếp theo. Sau đó nó lên lịch một hàm gọi lại để xử lý dữ liệu.
Các hàm gọi lại được lên lịch nên tuân theo các nguyên tắc thiết kế trình xử lý ngắt được trình bày bên dưới. Điều này để tránh các vấn đề phát sinh từ hoạt động I/O và việc sửa đổi dữ liệu được chia sẻ có thể phát sinh trong bất kỳ mã nào chiếm quyền ưu tiên của vòng lặp chương trình chính.
Thời gian thực thi cần được xem xét liên quan đến tần suất có thể xảy ra ngắt. Nếu một ngắt xảy ra trong khi hàm gọi lại trước đó đang thực thi, thêm một thực thể của hàm gọi lại sẽ được xếp vào hàng đợi để thực thi; thực thể này sẽ chạy sau khi thực thể hiện tại hoàn thành. Do đó, tốc độ lặp lại ngắt cao và liên tục có nguy cơ gây ra tăng trưởng hàng đợi không bị kiểm soát và cuối cùng thất bại với lỗi RuntimeError.
Nếu hàm gọi lại được truyền cho schedule() là một phương thức bị ràng buộc, hãy xem lưu ý trong "Tạo đối tượng Python".
Ngoại lệ¶
Nếu một ISR đưa ra ngoại lệ, nó sẽ không lan truyền đến vòng lặp chính. Ngắt sẽ bị vô hiệu hóa trừ khi ngoại lệ được xử lý bởi mã ISR.
Giao tiếp với asyncio¶
Khi một ISR chạy, nó có thể chiếm quyền ưu tiên của bộ lên lịch asyncio. Nếu ISR thực hiện một thao tác asyncio, hoạt động của bộ lên lịch có thể bị gián đoạn. Điều này áp dụng cho dù ngắt là cứng hay mềm và cũng áp dụng nếu ISR đã chuyển thực thi cho một hàm khác thông qua micropython.schedule. Đặc biệt, tạo hoặc hủy tác vụ là không hợp lệ trong ngữ cảnh ISR. Cách an toàn để tương tác với asyncio là triển khai một coroutine với đồng bộ hóa được thực hiện bởi asyncio.ThreadSafeFlag. Đoạn mã sau minh họa việc tạo một tác vụ để đáp lại một ngắt:
tsf = asyncio.ThreadSafeFlag()
def isr(_): # Interrupt handler
tsf.set()
async def foo():
while True:
await tsf.wait()
asyncio.create_task(bar())
Trong ví dụ này, sẽ có một lượng độ trễ thay đổi giữa quá trình thực thi ISR và quá trình thực thi foo(). Điều này vốn có trong lập lịch hợp tác. Độ trễ tối đa phụ thuộc vào ứng dụng và nền tảng nhưng thường có thể được đo bằng hàng chục mili giây.
Các vấn đề chung¶
Đây chỉ là phần giới thiệu ngắn gọn về chủ đề lập trình thời gian thực. Người mới bắt đầu nên lưu ý rằng các lỗi thiết kế trong chương trình thời gian thực có thể dẫn đến các sự cố đặc biệt khó chẩn đoán. Điều này là vì chúng có thể xảy ra hiếm khi và ở các khoảng thời gian về cơ bản là ngẫu nhiên. Điều quan trọng là phải thiết kế đúng ngay từ đầu và dự đoán các vấn đề trước khi chúng phát sinh. Cả trình xử lý ngắt lẫn chương trình chính đều cần được thiết kế với sự hiểu biết về các vấn đề sau đây.
Thiết kế trình xử lý ngắt¶
Như đã đề cập ở trên, ISR nên được thiết kế để đơn giản nhất có thể. Chúng nên luôn trả về trong một khoảng thời gian ngắn, có thể dự đoán. Điều này quan trọng vì khi ISR đang chạy, vòng lặp chính không chạy: không thể tránh khỏi rằng vòng lặp chính trải qua các khoảng dừng trong quá trình thực thi tại các điểm ngẫu nhiên trong mã. Những khoảng dừng như vậy có thể là nguồn gốc của các lỗi khó chẩn đoán, đặc biệt nếu thời gian của chúng dài hoặc biến đổi. Để hiểu được các hàm ý của thời gian chạy ISR, cần có hiểu biết cơ bản về mức độ ưu tiên ngắt.
Các ngắt được tổ chức theo một sơ đồ ưu tiên. Mã ISR chính nó có thể bị ngắt bởi một ngắt có mức độ ưu tiên cao hơn. Điều này có hàm ý nếu hai ngắt chia sẻ dữ liệu (xem Vùng Tới Hạn bên dưới). Nếu một ngắt như vậy xảy ra, nó sẽ chèn một độ trễ vào mã ISR. Nếu một ngắt có mức độ ưu tiên thấp hơn xảy ra trong khi ISR đang chạy, nó sẽ bị hoãn lại cho đến khi ISR hoàn thành: nếu độ trễ quá lâu, ngắt có mức độ ưu tiên thấp hơn có thể thất bại. Một vấn đề thêm với các ISR chậm là trường hợp một ngắt thứ hai cùng loại xảy ra trong quá trình thực thi. Ngắt thứ hai sẽ được xử lý khi ngắt đầu tiên kết thúc. Tuy nhiên, nếu tốc độ ngắt đến liên tục vượt quá khả năng của ISR để phục vụ chúng, kết quả sẽ không tốt.
Do đó, các cấu trúc vòng lặp nên được tránh hoặc giảm thiểu. I/O đến các thiết bị khác ngoài thiết bị đang ngắt thường nên tránh: I/O như truy cập đĩa, câu lệnh print và truy cập UART là tương đối chậm, và thời gian của nó có thể thay đổi. Một vấn đề thêm ở đây là các hàm hệ thống tệp không phải là tái nhập: sử dụng I/O hệ thống tệp trong ISR và chương trình chính sẽ là nguy hiểm. Quan trọng hơn, mã ISR không nên chờ đợi một sự kiện. I/O được chấp nhận nếu mã có thể được đảm bảo trả về trong một khoảng thời gian có thể dự đoán, ví dụ bật tắt một chân (pin) hay đèn LED. Truy cập thiết bị đang ngắt thông qua I2C hoặc SPI có thể là cần thiết nhưng thời gian cần cho các truy cập đó nên được tính toán hoặc đo lường và tác động của nó lên ứng dụng cần được đánh giá.
Thường có nhu cầu chia sẻ dữ liệu giữa ISR và vòng lặp chính. Điều này có thể được thực hiện thông qua các biến toàn cục hoặc thông qua biến lớp hay thực thể. Các biến thường là kiểu số nguyên hay boolean, hay mảng số nguyên hoặc byte (mảng số nguyên được cấp phát trước cung cấp truy cập nhanh hơn danh sách). Khi nhiều giá trị được sửa đổi bởi ISR, cần xem xét trường hợp ngắt xảy ra vào thời điểm chương trình chính đã truy cập một số, nhưng không phải tất cả, các giá trị. Điều này có thể dẫn đến sự không nhất quán.
Hãy xem xét thiết kế sau. Một ISR lưu dữ liệu đến trong một bytearray, sau đó thêm số byte nhận được vào một số nguyên đại diện cho tổng số byte sẵn sàng để xử lý. Chương trình chính đọc số byte, xử lý các byte, sau đó xóa số byte sẵn sàng. Điều này sẽ hoạt động cho đến khi một ngắt xảy ra ngay sau khi chương trình chính đã đọc số byte. ISR đưa dữ liệu được thêm vào bộ đệm và cập nhật số byte nhận được, nhưng chương trình chính đã đọc số, vì vậy nó xử lý dữ liệu ban đầu nhận được. Các byte mới đến bị mất.
Có nhiều cách để tránh nguy hiểm này, đơn giản nhất là sử dụng bộ đệm vòng. Nếu không thể sử dụng cấu trúc có tính an toàn luồng vốn có, các cách khác được mô tả bên dưới.
Tính tái nhập¶
Một nguy hiểm tiềm ẩn có thể xảy ra nếu một hàm hay phương thức được chia sẻ giữa chương trình chính và một hoặc nhiều ISR, hoặc giữa nhiều ISR. Vấn đề ở đây là hàm có thể bị ngắt và một thực thể khác của hàm đó chạy. Nếu điều này xảy ra, hàm phải được thiết kế để tái nhập. Cách thực hiện điều này là một chủ đề nâng cao nằm ngoài phạm vi của hướng dẫn này.
Vùng tới hạn¶
Một ví dụ về vùng tới hạn của mã là vùng truy cập nhiều hơn một biến có thể bị ảnh hưởng bởi ISR. Nếu ngắt xảy ra giữa các lần truy cập các biến riêng lẻ, các giá trị của chúng sẽ không nhất quán. Đây là một trường hợp của một mối nguy hiểm được gọi là điều kiện race: ISR và vòng lặp chương trình chính tranh nhau để thay đổi các biến. Để tránh sự không nhất quán, phải sử dụng một phương tiện để đảm bảo rằng ISR không thay đổi các giá trị trong suốt thời gian của vùng tới hạn. Một cách để đạt được điều này là phát hành machine.disable_irq() trước khi bắt đầu phần đó, và machine.enable_irq() ở cuối. Đây là một ví dụ về cách tiếp cận này:
import machine
import micropython
import array
import random
import time
micropython.alloc_emergency_exception_buf(100)
class BoundsException(Exception):
pass
ARRAYSIZE = const(20)
index = 0
data = array.array('i', [0] * ARRAYSIZE)
def callback1(t):
global data, index
for x in range(5):
data[index] = random.getrandbits(30) # simulate input
index += 1
if index >= ARRAYSIZE:
raise BoundsException('Array bounds exceeded')
tim = machine.Timer(-1, freq=100, callback=callback1, hard=True)
for loop in range(1000):
if index > 0:
irq_state = machine.disable_irq() # Start of critical section
for x in range(index):
print(data[x])
index = 0
machine.enable_irq(irq_state) # End of critical section
print('loop {}'.format(loop))
time.sleep_ms(1)
tim.deinit()
Một vùng tới hạn có thể bao gồm một dòng mã và một biến duy nhất. Hãy xem xét đoạn mã sau.
count = 0
def cb(): # An interrupt callback
count += 1
def main():
# Code to set up the interrupt callback omitted
while True:
count += 1
Ví dụ này minh họa một nguồn lỗi tinh vi. Dòng count += 1 trong vòng lặp chính mang một nguy hiểm điều kiện race cụ thể được gọi là đọc-sửa đổi-ghi. Đây là nguyên nhân điển hình của lỗi trong các hệ thống thời gian thực. Trong vòng lặp chính, MicroPython đọc giá trị của count, thêm 1 vào đó, và ghi lại. Trong các trường hợp hiếm, ngắt xảy ra sau khi đọc và trước khi ghi. Ngắt sửa đổi count nhưng thay đổi của nó bị ghi đè bởi vòng lặp chính khi ISR trả về. Trong một hệ thống thực, điều này có thể dẫn đến các thất bại hiếm gặp, không thể dự đoán.
Như đã đề cập ở trên, cần đặc biệt cẩn thận nếu một thực thể của kiểu tích hợp Python được sửa đổi trong mã chính và thực thể đó được truy cập trong ISR. Mã thực hiện việc sửa đổi nên được coi là vùng tới hạn để đảm bảo rằng thực thể ở trạng thái hợp lệ khi ISR chạy.
Cần đặc biệt cẩn thận nếu một tập dữ liệu được chia sẻ giữa các ISR khác nhau. Nguy hiểm ở đây là ngắt có mức độ ưu tiên cao hơn có thể xảy ra khi ngắt có mức độ ưu tiên thấp hơn đã cập nhật một phần dữ liệu được chia sẻ. Xử lý tình huống này là một chủ đề nâng cao nằm ngoài phạm vi của phần giới thiệu này, ngoại trừ việc lưu ý rằng các đối tượng mutex được mô tả bên dưới đôi khi có thể được sử dụng.
Tắt ngắt trong suốt thời gian của vùng tới hạn là cách thông thường và đơn giản nhất để tiến hành, nhưng nó tắt tất cả các ngắt thay vì chỉ ngắt có khả năng gây ra vấn đề. Nói chung, không mong muốn tắt ngắt trong thời gian dài. Trong trường hợp ngắt bộ định thời, nó gây ra sự biến đổi về thời gian khi hàm gọi lại xảy ra. Trong trường hợp ngắt thiết bị, nó có thể dẫn đến thiết bị được phục vụ quá muộn với khả năng mất dữ liệu hoặc lỗi tràn trong phần cứng thiết bị. Giống như ISR, một vùng tới hạn trong mã chính nên có thời gian ngắn, có thể dự đoán.
Một cách tiếp cận để xử lý các vùng tới hạn làm giảm đáng kể thời gian ngắt bị tắt là sử dụng một đối tượng gọi là mutex (tên xuất phát từ khái niệm loại trừ lẫn nhau). Chương trình chính khóa mutex trước khi chạy vùng tới hạn và mở khóa ở cuối. ISR kiểm tra xem mutex có bị khóa không. Nếu có, nó tránh vùng tới hạn và trả về. Thách thức thiết kế là xác định ISR nên làm gì trong trường hợp bị từ chối truy cập vào các biến tới hạn. Một ví dụ đơn giản về mutex có thể được tìm thấy tại đây. Lưu ý rằng mã mutex thực sự tắt ngắt, nhưng chỉ trong suốt tám lệnh máy: lợi ích của cách tiếp cận này là các ngắt khác hầu như không bị ảnh hưởng.
Ngắt và REPL¶
Các trình xử lý ngắt, chẳng hạn như những trình xử lý liên quan đến bộ định thời, có thể tiếp tục chạy sau khi chương trình kết thúc. Điều này có thể tạo ra các kết quả không mong đợi trong trường hợp bạn có thể đã mong đợi đối tượng đang tạo ra hàm gọi lại đã vượt ra ngoài phạm vi. Ví dụ trên một OpenMV Cam:
def bar():
foo = machine.Timer(-1, freq=4, callback=lambda t: print('.', end=''), hard=True)
bar()
Điều này tiếp tục chạy cho đến khi bộ định thời được tắt rõ ràng hoặc bảng mạch được khởi động lại bằng Ctrl-D.