Thư viện ngoại vi tiêu chuẩn. Kết nối thư viện ngoại vi tiêu chuẩn với bất kỳ họ STM32 nào

Sự tương tác của mã người dùng với các thanh ghi lõi và ngoại vi của bộ vi điều khiển STM32 có thể được thực hiện theo hai cách: sử dụng thư viện chuẩn hoặc sử dụng bộ đoạn mã (gợi ý phần mềm). Sự lựa chọn giữa chúng phụ thuộc vào khối lượng ký ức riêng bộ điều khiển, tốc độ yêu cầu, thời hạn phát triển. Bài viết phân tích đặc điểm cấu trúc, ưu nhược điểm của các bộ đoạn mã cho bộ vi điều khiển họ STM32F1 và STM32L0 do STMicroelectronics sản xuất.

Một trong những lợi thế của việc sử dụng bộ vi điều khiển STMicroelectronics là có nhiều công cụ phát triển: tài liệu, bảng phát triển, phần mềm.

Phần mềm dành cho STM32 bao gồm phần mềm độc quyền do STMicroelectronics sản xuất, nguồn mở và phần mềm thương mại.

Phần mềm STMicroelectronics có những ưu điểm quan trọng. Thứ nhất, nó có sẵn cho tải xuống miễn phí. Thứ hai, thư viện phần mềmđược trình bày dưới dạng mã nguồn - người dùng có thể tự sửa đổi mã, có tính đến các hạn chế nhỏ được mô tả trong thỏa thuận cấp phép.

Thư viện STMicroelectronics tuân thủ ANSI-C và có thể được chia theo mức độ trừu tượng (Hình 1):

  • CMSIS (Lớp truy cập ngoại vi lõi) - cấp độ đăng ký lõi và ngoại vi, thư viện ARM;
  • Lớp trừu tượng phần cứng – thư viện cấp thấp: thư viện ngoại vi tiêu chuẩn, bộ đoạn mã;
  • Middleware – thư viện cấp trung: hệ điều hành thời gian thực (RTOS), hệ thống tệp, USB, TCP/IP, Bluetooth, Display, ZigBee, Touch Sensing và các thứ khác;
  • Trường ứng dụng - thư viện cấp độ ứng dụng: giải pháp âm thanh, điều khiển động cơ, ô tô và công nghiệp.

Hình 1 cho thấy để tương tác với cấp độ CMSIS, STMicroelectronics cung cấp việc sử dụng hai công cụ chính - thư viện tiêu chuẩn và đoạn mã.

Thư viện tiêu chuẩn là một bộ trình điều khiển. Mỗi trình điều khiển cung cấp cho người dùng các chức năng và định nghĩa để làm việc với một khối ngoại vi cụ thể (SPI, USART, ADC, v.v.). Người dùng không tương tác trực tiếp với các thanh ghi cấp độ CMSIS.

Bộ đoạn mã có hiệu quả cao ví dụ lập trình, sử dụng quyền truy cập trực tiếp vào các thanh ghi CMSIS. Các nhà phát triển phần mềm có thể sử dụng việc triển khai các hàm từ các ví dụ này trong mã của riêng họ.

Mỗi phương pháp có ưu điểm và nhược điểm. Việc lựa chọn giữa chúng được thực hiện có tính đến dung lượng FLASH và RAM có sẵn, tốc độ cần thiết, thời gian phát triển, kinh nghiệm của lập trình viên và các trường hợp khác.

cấp độ CMSIS

Bộ vi điều khiển là một chip tương tự kỹ thuật số phức tạp bao gồm lõi xử lý, bộ nhớ, thiết bị ngoại vi, bus kỹ thuật số, v.v. Tương tác với mỗi khối xảy ra bằng cách sử dụng các thanh ghi.

Theo quan điểm của lập trình viên, bộ vi điều khiển đại diện cho một không gian bộ nhớ. Nó không chỉ chứa RAM, FLASH và EEPROM mà còn chứa các thanh ghi chương trình. Mỗi thanh ghi phần cứng tương ứng với một ô nhớ. Vì vậy, để ghi dữ liệu vào một thanh ghi hoặc trừ giá trị của nó, người lập trình cần truy cập vào vị trí tương ứng trong không gian địa chỉ.

Một người có một số đặc điểm về nhận thức. Ví dụ, những cái tên tượng trưng được anh ta cảm nhận tốt hơn nhiều so với địa chỉ của các ô bộ nhớ. Điều này đặc biệt đáng chú ý khi một số lượng lớn tế bào được sử dụng. Trong bộ vi điều khiển ARM, số lượng thanh ghi và số lượng ô được sử dụng vượt quá một nghìn. Để làm cho mọi việc dễ dàng hơn, cần phải định nghĩa các con trỏ tượng trưng. Quyết định này được thực hiện ở cấp độ CMSIS.

Ví dụ: để đặt trạng thái của các chân của cổng A, bạn cần ghi dữ liệu vào thanh ghi GPIOA_ODR. Điều này có thể được thực hiện theo hai cách - sử dụng một con trỏ có địa chỉ ô 0xEBFF FCFF với độ lệch 0x14 hoặc sử dụng một con trỏ có tên tượng trưng GPIOA và cấu trúc tạo sẵn xác định độ lệch. Rõ ràng, lựa chọn thứ hai dễ hiểu hơn nhiều.

CMSIS cũng thực hiện các chức năng khác. Nó được thực hiện dưới hình thức nhóm tiếp theo các tập tin:

  • startup_stm32l0xx.s chứa mã khởi động trình biên dịch mã cho Cortex-M0+ và bảng vectơ ngắt. Sau khi quá trình khởi tạo bắt đầu hoàn tất, điều khiển trước tiên được chuyển sang hàm SystemInit() (các giải thích sẽ được đưa ra bên dưới), sau đó đến hàm chính int main(void);
  • stm32l0xx.h chứa các định nghĩa cần thiết để thực hiện các thao tác bit cơ bản và định nghĩa về loại bộ vi xử lý được sử dụng;
  • system_stm32l0xx.c/.h. Sau lần khởi tạo đầu tiên, hàm SystemInit() sẽ được thực thi. Nó thực hiện thiết lập ban đầu các thiết bị ngoại vi hệ thống, thời gian của khối RCC;
  • stm32l0yyxx.h – tệp triển khai cho các bộ vi điều khiển cụ thể (ví dụ: stm32l051xx.h). Trong đó, các con trỏ ký tự, cấu trúc dữ liệu, hằng số bit và độ lệch được xác định.

Tương tác với CMSIS. Thư viện và đoạn mã tiêu chuẩn

Số lượng thanh ghi cho bộ vi điều khiển STM32 trong hầu hết các kiểu máy đều vượt quá một nghìn. Nếu bạn sử dụng quyền truy cập trực tiếp vào sổ đăng ký, mã người dùng sẽ không thể đọc được và hoàn toàn không thể sử dụng được để hỗ trợ và hiện đại hóa. Vấn đề này có thể được giải quyết bằng cách sử dụng thư viện ngoại vi tiêu chuẩn.

Thư viện ngoại vi tiêu chuẩn là một tập hợp các trình điều khiển cấp thấp. Mỗi trình điều khiển cung cấp cho người dùng một bộ chức năng để làm việc với thiết bị ngoại vi. Bằng cách này, người dùng sử dụng các chức năng thay vì truy cập trực tiếp vào các thanh ghi. Trong trường hợp này, cấp độ CMSIS bị ẩn khỏi lập trình viên (Hình 2a).

Cơm. 2. Tương tác với CMSIS bằng thư viện chuẩn (a) và đoạn mã (b)

Ví dụ: tương tác với các cổng I/O trong STM32L0 được triển khai bằng trình điều khiển được tạo ở dạng hai tệp: stm32l0xx_hal_gpio.h và stm32l0xx_hal_gpio.c. stm32l0xx_hal_gpio.h cung cấp các định nghĩa cơ bản về kiểu và hàm, còn stm32l0xx_hal_gpio.c cung cấp cách triển khai chúng.

Cách tiếp cận này có những ưu điểm khá rõ ràng (Bảng 1):

  • Tạo mã nhanh. Người lập trình không cần nghiên cứu danh sách các thanh ghi. Anh ta ngay lập tức bắt đầu làm việc ở cấp độ cao hơn. Ví dụ, để giao tiếp trực tiếp với cổng I/O trên STM32L0, bạn phải biết và có khả năng vận hành 11 thanh ghi điều khiển/trạng thái, hầu hết trong số đó có tới 32 bit có thể cấu hình được. Khi sử dụng trình điều khiển thư viện, chỉ cần thành thạo tám chức năng là đủ.
  • Sự đơn giản và rõ ràng của mã. Mã người dùng không bị tắc với tên đăng ký, nó có thể minh bạch và dễ đọc, điều này rất quan trọng khi làm việc với nhóm phát triển.
  • Mức độ trừu tượng cao. Khi sử dụng thư viện chuẩn, mã hóa ra khá độc lập với nền tảng. Ví dụ: nếu bạn thay đổi bộ vi điều khiển STM32L0 thành bộ vi điều khiển STM32F0, một số mã hoạt động với các cổng I/O sẽ không cần phải thay đổi chút nào.

Bảng 1. So sánh các phương pháp triển khai mã tùy chỉnh

Tham số so sánh Khi sử dụng tiêu chuẩn
thư viện ngoại vi
Khi sử dụng bộ đoạn mã
Kích thước mã trung bình tối thiểu
chi phí RAM trung bình tối thiểu
Hiệu suất trung bình tối đa
Khả năng đọc mã xuất sắc thấp
Mức độ độc lập của nền tảng trung bình ngắn
Tốc độ tạo chương trình cao thấp

Sự hiện diện của lớp vỏ bổ sung dưới dạng trình điều khiển cũng có những nhược điểm rõ ràng (Bảng 1):

  • Tăng khối lượng mã chương trình. Các chức năng được triển khai trong mã thư viện yêu cầu không gian thêm trong tâm trí.
  • Chi phí RAM tăng do tăng số lượng biến cục bộ và sử dụng cấu trúc dữ liệu cồng kềnh.
  • Giảm hiệu suất do tăng chi phí khi gọi các hàm thư viện.

Chính sự tồn tại của những thiếu sót này đã dẫn đến việc người dùng thường bị buộc phải tối ưu hóa mã - triển khai độc lập các chức năng để tương tác với CMSIS, tối ưu hóa các chức năng thư viện bằng cách loại bỏ tất cả những thứ không cần thiết, sao chép trực tiếp việc triển khai các chức năng thư viện vào mã của họ, sử dụng lệnh __INLINE để tăng tốc độ thực thi. Kết quả là, người ta đã dành thêm thời gian để tinh chỉnh mã.

STMicroelectronics, gặp gỡ các nhà phát triển một nửa, đã phát hành bộ sưu tập đoạn mã STM32SnippetsF0 và STM32SnippetsL0.

Các đoạn mã được bao gồm trong mã người dùng (Hình 2b).

Việc sử dụng đoạn mã mang lại những lợi ích rõ ràng:

  • tăng hiệu quả và tốc độ của mã;
  • giảm phạm vi của chương trình;
  • Giảm dung lượng RAM được sử dụng và tải trên ngăn xếp.

Tuy nhiên, điều đáng chú ý là những nhược điểm:

  • làm giảm tính đơn giản và rõ ràng của mã do “nhiễm” tên đăng ký và việc triển khai độc lập các chức năng cấp thấp;
  • sự biến mất của nền tảng độc lập.

Vì vậy, sự lựa chọn giữa thư viện chuẩn và đoạn mã là không rõ ràng. Trong hầu hết các trường hợp, điều đáng nói không phải là về sự cạnh tranh mà là về việc sử dụng lẫn nhau của chúng. Ở giai đoạn đầu cho xây dựng nhanh chóng Code “đẹp”, sử dụng hợp lý trình điều khiển tiêu chuẩn. Nếu cần tối ưu hóa, bạn có thể chuyển sang các đoạn mã làm sẵn để không lãng phí thời gian phát triển các chức năng tối ưu của riêng mình.

Thư viện tiêu chuẩn của trình điều khiển và đoạn mã STM32F0 và STM32L0 (Bảng 2) có sẵn để tải xuống miễn phí trên trang web www.st.com.

Bảng 2. Thư viện cấp thấp cho STM32F10 và STM32L0

Khi làm quen kỹ hơn với các đoạn trích, cũng như với bất kỳ phần mềm nào, nên bắt đầu bằng cách xem xét các tính năng của thỏa thuận cấp phép.

Thỏa thuận cấp phép

Bất kỳ lập trình viên có trách nhiệm nào trước khi sử dụng bên thứ ba sản phẩm phần mềm nghiên cứu cẩn thận thỏa thuận cấp phép. Mặc dù thực tế là các bộ sưu tập đoạn trích do ST Microelectronics sản xuất không yêu cầu cấp phép và có sẵn để tải xuống miễn phí, điều này không có nghĩa là không có hạn chế đối với việc sử dụng chúng.

Thỏa thuận cấp phép đi kèm với tất cả các sản phẩm có thể tải xuống miễn phí do STMicroelectronics sản xuất. Sau khi tải STM32SnippetsF0 và STM32SnippetsL0 vào thư mục gốc Có thể dễ dàng tìm thấy tài liệu Thỏa thuận cấp phép MCD-ST Liberty SW V2.pdf, trong đó giới thiệu cho người dùng các quy định sử dụng phần mềm này.

Thư mục Project chứa các thư mục con với các ví dụ cho các thiết bị ngoại vi cụ thể, các dự án được tạo sẵn cho ARM Keil và EWARM, cũng như các tệp main.c.

Ra mắt và tính năng sử dụng bộ đoạn mã STM32SnippetsF0 và STM32SnippetsL0

Điểm đặc biệt của các bộ đoạn mã này là sự phụ thuộc vào nền tảng của chúng. Chúng được thiết kế để làm việc với các bảng cụ thể. STM32SnippetsL0 sử dụng bảng Discovery STM32L053 và STM32SnippetsF0 sử dụng bảng Discovery STM32F072.

Khi sử dụng bảng phát triển riêng mã và dự án phải được thay đổi, điều này sẽ được thảo luận chi tiết hơn ở phần cuối.

Để chạy ví dụ, bạn cần hoàn thành một số bước:

  • chạy dự án đã hoàn thành từ thư mục có ví dụ được yêu cầu. Để đơn giản, bạn có thể sử dụng dự án làm sẵnđối với môi trường ARM Keil hoặc EWARM, nằm trong thư mục MDK-ARM\ và EWARM\ tương ứng;
  • bật nguồn cho bo mạch phát triển Discovery STM32L053 Discovery/STM32F072 Discovery;
  • Kết nối nguồn điện của bảng gỡ lỗi với PC bằng cáp USB. Nhờ trình gỡ lỗi ST-Link/V2 tích hợp sẵn, không cần lập trình viên bổ sung;
  • mở, cấu hình và chạy dự án;
    • Đối với ARM Keil:
      • Chủ đề mở;
      • biên dịch dự án – Dự án → Xây dựng lại tất cả các tệp mục tiêu;
      • tải nó vào bộ điều khiển – Gỡ lỗi → Bắt đầu/Dừng phiên gỡ lỗi;
      • chạy chương trình trong cửa sổ Debug → Run (F5).
    • Đối với EWARM:
      • Chủ đề mở;
      • biên dịch dự án – Dự án → Xây dựng lại tất cả;
      • tải nó vào bộ điều khiển – Dự án → Gỡ lỗi;
      • chạy chương trình trong cửa sổ Debug → Go (F5).
  • thực hiện thử nghiệm theo thuật toán được mô tả trong main.c.

Để phân tích mã chương trình, hãy xem xét một ví dụ cụ thể từ STM32SnippetsL0: Projects\LPUART\01_WakeUpFromLPM\.

Chạy một ví dụ cho LPUART

Một tính năng đặc biệt của bộ vi điều khiển mới thuộc họ STM32L0 dựa trên lõi Cortex-M0+ là khả năng thay đổi năng động tiêu dùng do số lượng lớn những đổi mới. Một trong những cải tiến này là sự xuất hiện của các thiết bị ngoại vi Nguồn điện thấp: bộ hẹn giờ LPTIM 16 bit và bộ thu phát LPUART. Các khối này có khả năng xung nhịp độc lập với xung nhịp của bus ngoại vi APB chính. Nếu cần giảm mức tiêu thụ điện năng, có thể giảm tần số hoạt động của bus APB (PCLK) và bản thân bộ điều khiển có thể được chuyển sang chế độ tiêu thụ điện năng thấp. Đồng thời, các thiết bị ngoại vi Low Power tiếp tục hoạt động với hiệu suất tối đa.

Hãy xem xét một ví dụ từ thư mục Projects\LPUART\01_WakeUpFromLPM\, xem xét khả năng hoạt động độc lập của LPUART ở chế độ tiêu thụ thấp.

Khi mở một dự án trong môi trường ARM Keil, chỉ có ba tệp được hiển thị: startup_stm32l053xx.s, system_stm32l0xx.c và main.c (Hình 4). Nếu thư viện chuẩn được sử dụng thì cần phải thêm các tệp trình điều khiển vào dự án.

Chức năng và phân tích cấu trúc tệp Main.c

Chương trình ví dụ đã chọn được thực hiện theo nhiều giai đoạn.

Sau khi bắt đầu, hàm SystemInit(), được triển khai trong system_stm32l0xx.c, sẽ được khởi chạy. Nó cấu hình các tham số của khối đồng hồ RCC (thời gian và tần số hoạt động). Tiếp theo, điều khiển được chuyển sang hàm main int main(void). Nó khởi tạo các thiết bị ngoại vi của người dùng - cổng đầu vào/đầu ra, LPUART - sau đó bộ điều khiển được chuyển sang chế độ STOP tiêu thụ thấp. Trong đó, ngoại vi và lõi thông thường bị dừng lại, chỉ LPUART hoạt động. Nó chờ quá trình truyền dữ liệu bắt đầu từ thiết bị bên ngoài. Khi bit bắt đầu đến, LPUART đánh thức hệ thống và nhận tin nhắn. Việc tiếp nhận đi kèm với sự nhấp nháy của đèn LED trên bảng gỡ lỗi. Sau đó, bộ điều khiển được chuyển về trạng thái STOP và chờ lần truyền dữ liệu tiếp theo nếu không phát hiện thấy lỗi.

Truyền dữ liệu xảy ra bằng cổng COM ảo và phần mềm bổ sung.

Hãy xem main.c từ dự án của chúng tôi. Tệp này là tệp C tiêu chuẩn. Tính năng chính của nó là tự ghi tài liệu - sự hiện diện của các nhận xét, giải thích và khuyến nghị chi tiết. Phần giải thích bao gồm một số phần:

  • tiêu đề cho biết tên tệp, phiên bản, ngày tháng, tác giả và giải thích ngắn gọn về mục đích;
  • mô tả trình tự thiết lập các thiết bị ngoại vi của hệ thống (các tính năng cụ thể của RCC): FLASH, RAM, hệ thống cấp nguồn và đồng hồ, bus ngoại vi, v.v.;
  • danh sách các tài nguyên vi điều khiển được sử dụng (MCU Resources);
  • giải thích ngắn gọn về việc sử dụng ví dụ này(Cách sử dụng ví dụ này);
  • giải thích ngắn gọn về việc kiểm tra ví dụ và thuật toán triển khai nó (Cách kiểm tra ví dụ này).

Hàm int main(void) có dạng thu gọn và được trang bị các chú thích, trong Liệt kê 1, để rõ ràng hơn, chúng được dịch sang tiếng Nga.

Liệt kê 1. Ví dụ triển khai hàm main

int chính(void)
{
/* Khi bắt đầu thực hiện phần này, khi các đơn vị hệ thống đã được cấu hình trong hàm SystemInit(), được triển khai trong system_stm32l0xx.c. */
/*cấu hình các thiết bị ngoại vi */
Định cấu hình_GPIO_LED();
Định cấu hình_GPIO_LPUART();
Định cấu hình_LPUART();
Định cấu hình_LPM_Stop();
/*kiểm tra lỗi trong quá trình nhận */
while (!error) /* vòng lặp vô tận */
{
/*đợi LPUART sẵn sàng và chuyển sang chế độ STOP */
if((LPUART1->ISR & USART_ISR_REACK) == USART_ISR_REACK)
{
__WFI();
}
}
/* khi xảy ra lỗi */
SysTick_Config(2000); /* đặt khoảng thời gian ngắt bộ hẹn giờ hệ thống thành 1 ms */
trong khi(1);
}

Tệp main.c khai báo và xác định các chức năng cấu hình ngoại vi và hai chức năng xử lý ngắt. Hãy xem xét các tính năng của họ.

Ví dụ dưới đây sử dụng bốn hàm cấu hình (Liệt kê 2). Tất cả đều không có đối số và không trả về giá trị. Mục đích chính của chúng là nhanh chóng và với số lượng mã cần thiết ít nhất để khởi tạo các thiết bị ngoại vi. Điều này đạt được thông qua hai tính năng: sử dụng quyền truy cập trực tiếp vào các thanh ghi và sử dụng chỉ thị __INLINE (Liệt kê 3).

Liệt kê 2. Khai báo các hàm cấu hình ngoại vi

void Cấu hình_GPIO_LED(void);
void Cấu hình_GPIO_LPUART(void);
void Cấu hình_LPUART(void);
void Cấu hình_LPM_Stop(void);

Liệt kê 3. Ví dụ triển khai hàm __INLINE với quyền truy cập trực tiếp vào các thanh ghi LPUART

INLINE void Cấu hình_LPUART(void)
{
/* (1) Kích hoạt đồng hồ giao diện nguồn */
/* (2) Vô hiệu hóa thanh ghi bảo vệ sao lưu để cho phép truy cập vào miền đồng hồ RTC */
/* (3) Bật LSE */
/* (4) Đợi LSE sẵn sàng */
/* (5) Kích hoạt thanh ghi bảo vệ sao lưu để cho phép truy cập vào miền đồng hồ RTC */
/* (6) LSE được ánh xạ trên LPUART */
/* (7) Kích hoạt đồng hồ ngoại vi LPUART */
/* Cấu hình LPUART */
/* (8) lấy mẫu quá mức 16, 9600 baud */
/* (9) 8 bit dữ liệu, 1 bit bắt đầu, 1 bit dừng, không có tính chẵn lẻ, chế độ nhận, chế độ dừng */
/* (10) Đặt mức độ ưu tiên cho LPUART1_IRQn */
/* (11) Kích hoạt LPUART1_IRQn */
RCC->APB1ENR |= (RCC_APB1ENR_PWREN); /* (1) */
PWR->CR |= PWR_CR_DBP; /* (2) */
RCC->CSR |= RCC_CSR_LSEON; /* (3) */
trong khi ((RCC->CSR & (RCC_CSR_LSERDY)) != (RCC_CSR_LSERDY)) /*(4)*/
{
/* thêm thời gian ở đây để có một ứng dụng mạnh mẽ */
}
PWR->CR &=~ PWR_CR_DBP; /* (5) */
RCC->CCIPR |= RCC_CCIPR_LPUART1SEL; /* (6) */
RCC->APB1ENR |= RCC_APB1ENR_LPUART1EN; /*(7) */
LPUART1->BRR = 0x369; /* (số 8) */
LPUART1->CR1 = USART_CR1_UESM | USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_UE; /* (9) */
NVIC_SetPriority(LPUART1_IRQn, 0); /* (10) */
NVIC_EnableIRQ(LPUART1_IRQn); /* (mười một) */
}

Trình xử lý ngắt từ bộ đếm thời gian hệ thống và từ LPUART cũng sử dụng quyền truy cập trực tiếp vào các thanh ghi.

Do đó, việc giao tiếp với CMSIS được thực hiện mà không cần thư viện chuẩn. Mã hóa ra nhỏ gọn và hiệu quả cao. Tuy nhiên, khả năng đọc của nó sẽ giảm đi đáng kể do có quá nhiều quyền truy cập vào các thanh ghi.

Sử dụng đoạn trích trong quá trình phát triển của riêng bạn

Các bộ đoạn mã được đề xuất có những hạn chế: cần sử dụng bảng Discovery STM32L053 cho STM32SnippetsL0 và bảng Discovery STM32F072 cho STM32SnippetsF0.

Để sử dụng đoạn mã trong quá trình phát triển của mình, bạn sẽ cần thực hiện một số thay đổi. Trước tiên, bạn cần cấu hình lại dự án để bộ xử lý cần thiết. Để thực hiện việc này, bạn cần thay đổi tệp khởi đầu startup_stm32l053xx.s thành tệp của bộ điều khiển khác và xác định hằng số cần thiết: STM32L051xx, STM32L052xx, STM32L053xx, STM32L062xx, STM32L063xx, STM32L061xx, STM32F030, STM32F031, TM32F051 và các loại khác . Sau này, khi biên dịch stm32l0xx.h, tệp được yêu cầu với định nghĩa của các thiết bị ngoại vi của bộ điều khiển stm32l0yyxx.h (stm32l051xx.h/stm32l052xx.h/stm32l053xx.h/stm32l061xx.h/stm32l062xx.h/stm32l063) sẽ tự động được đưa vào. Thứ hai, bạn cần chọn lập trình viên phù hợp trong cài đặt thuộc tính dự án. Thứ ba, thay đổi code của các hàm trong ví dụ nếu chúng không đáp ứng được yêu cầu của ứng dụng người dùng.

Phần kết luận

Các bộ đoạn mã và thư viện ngoại vi tiêu chuẩn do ST Microelectronics tạo ra không loại trừ lẫn nhau. Chúng bổ sung cho nhau, tăng thêm tính linh hoạt khi tạo ứng dụng.

Thư viện tiêu chuẩn cho phép bạn nhanh chóng tạo mã rõ ràng với mức độ trừu tượng cao.

Các đoạn mã cho phép bạn cải thiện hiệu quả của mã - tăng năng suất và giảm dấu chân mã của bạn. Bộ nhớ flash và RAM.

Văn học

  1. Tóm tắt dữ liệu. STM32Đoạn F0. Gói chương trình cơ sở STM32F0xx Snippets. Rev. 1. – ST Vi Điện Tử, 2014.
  2. Tóm tắt dữ liệu. STM32Đoạn tríchL0. Gói chương trình cơ sở STM32F0xx Snippets. Rev. 1. – ST Vi Điện Tử, 2014.
  3. Thỏa thuận cấp phép MCD-ST Liberty SW V2.pdf Rơle điện cơ. Thông tin kĩ thuật. – ST Vi Điện Tử, 2011.
  4. Tóm tắt dữ liệu. 32L0538DISCOVERY Bộ khám phá dành cho bộ vi điều khiển STM32L053. Rev. 1. – ST Vi Điện Tử, 2014.
  5. http://www.st.com/.
Giới thiệu về ST Vi Điện Tử

Vì vậy, chúng ta đã tự đứng vững trở lại, nghĩa là chúng ta đã có mọi thứ chúng ta cần được kết nối với các chân của bộ vi điều khiển trên bảng STM32VL Discovery, chúng ta đã học cách nói bằng ngôn ngữ lập trình C, đã đến lúc tạo một dự án cho lớp một.

Viết một chương trình

Sau khi tạo và cấu hình dự án xong, bạn có thể bắt đầu viết chương trình thực tế. Theo thông lệ đối với tất cả các lập trình viên, chương trình đầu tiên được viết để hoạt động trên máy tính là chương trình hiển thị dòng chữ “HelloWorld” trên màn hình và đối với tất cả các bộ vi điều khiển, chương trình đầu tiên dành cho bộ vi điều khiển sẽ tạo ra đèn LED nhấp nháy. Chúng tôi sẽ không ngoại lệ với truyền thống này và sẽ viết một chương trình điều khiển đèn LED LD3 trên bảng Discovery STM32VL.

Sau khi tạo một dự án trống trong IAR, nó sẽ tạo ra mã chương trình tối thiểu:

Bây giờ chương trình của chúng ta sẽ luôn “quay” theo một vòng lặp trong khi.

Để điều khiển đèn LED, chúng ta cần kích hoạt tính năng xung nhịp của cổng mà nó được kết nối và định cấu hình đầu ra tương ứng của cổng vi điều khiển. Như chúng ta đã thảo luận trước đó trong phần đầu tiên, về việc cho phép đồng hồ cổng VỚI câu trả lời chút IOPCENđăng ký RCC_APB2ENR. Theo tài liệu " RM0041Thẩm quyền giải quyếtthủ công.pdf» kích hoạt đồng hồ cổng bus VỚIđược yêu cầu trong sổ đăng ký RCC_APB2ENRđặt bit IOPCEN trên mỗi đơn vị. Để đảm bảo rằng khi bit này được đặt, chúng ta không đặt lại các bit khác được đặt trong thanh ghi này, chúng ta cần áp dụng thao tác cộng logic (logic “OR”) cho trạng thái hiện tại của thanh ghi rồi ghi giá trị kết quả vào nội dung của sổ đăng ký. Theo cấu trúc của thư viện ST, việc truy cập vào giá trị thanh ghi để đọc và ghi được thực hiện thông qua một con trỏ tới cấu trúc RCC-> APB2 ENR. Do đó, nhớ lại tài liệu từ phần thứ hai, bạn có thể viết đoạn mã sau để thiết lập bit IOPCEN trong sổ đăng ký RCC_APB2ENR:

Như bạn có thể thấy từ tập tin “stm32f10x.h”, giá trị bit IOPCENđược định nghĩa là 0x00000010, tương ứng với bit thứ tư ( IOPCEN) đăng ký APB2ENR và khớp với giá trị được chỉ định trong biểu dữ liệu.

Bây giờ hãy cấu hình đầu ra theo cách tương tự 9 Hải cảng VỚI. Để làm được điều này, chúng ta cần cấu hình chân cổng này để xuất ra ở chế độ kéo đẩy. Thanh ghi có nhiệm vụ thiết lập chế độ vào/ra của cổng GPIOC_CRH, chúng tôi đã xem xét nó, mô tả của nó cũng nằm trong phần “Thanh ghi cấu hình cổng 7.2.2 cao” của biểu dữ liệu. Để cấu hình chế độ đầu ra thành đầu ra với tốc độ tối đa 2 MHz, cần có trong thanh ghi GPIOC_CRH cài đặt MODE9 thành một và đặt lại bit MODE9 về không. Các bit có nhiệm vụ thiết lập chế độ hoạt động ngõ ra làm chức năng chính với ngõ ra push-pull CNF9 CNF9 , để cấu hình chế độ vận hành mà chúng ta yêu cầu, cả hai bit này phải được đặt lại về 0.

Bây giờ chân của cổng mà đèn LED được kết nối được đặt thành đầu ra, để điều khiển đèn LED, chúng ta cần thay đổi trạng thái chân của cổng bằng cách đặt đầu ra thành logic một. Có hai cách để thay đổi trạng thái chân cổng, cách thứ nhất là ghi trực tiếp vào thanh ghi trạng thái cổng những nội dung đã thay đổi của thanh ghi cổng, giống như chúng ta đã cấu hình cổng. Phương pháp này không được khuyến nghị do có thể xảy ra tình huống ghi giá trị không chính xác vào thanh ghi cổng. Tình huống này có thể xảy ra nếu, trong quá trình thay đổi trạng thái của thanh ghi, kể từ thời điểm trạng thái thanh ghi đã được đọc và cho đến thời điểm trạng thái thay đổi được ghi vào thanh ghi, một số thiết bị ngoại vi hoặc ngắt sẽ thay đổi trạng thái của cổng này . Sau khi hoàn thành thao tác thay đổi trạng thái của thanh ghi, giá trị sẽ được ghi vào thanh ghi mà không tính đến những thay đổi đã xảy ra. Mặc dù khả năng xảy ra tình huống này là rất thấp nhưng vẫn nên sử dụng một phương pháp khác trong đó loại trừ tình huống được mô tả. Với mục đích này, có hai thanh ghi trong bộ vi điều khiển GPIOx_BSRRGPIOx_BRR. Khi ghi một logic vào bit thanh ghi được yêu cầu GPIOx_BRR thiết lập lại sẽ xảy rađầu ra cổng tương ứng về mức logic 0. Đăng ký GPIOx_BSRR có thể thực hiện cả cài đặt và đặt lại trạng thái của các chân cổng; để đặt chân cổng thành đơn vị logic, cần phải đặt các bit BSN, tương ứng với số bit được yêu cầu, các bit này nằm ở các thanh ghi thấp của byte. Để đặt lại trạng thái đầu ra của cổng về mức logic 0, bạn cần ghi các bit BRn các chân tương ứng, các bit này nằm ở các bit quan trọng nhất của thanh ghi cổng.

LED LD3 được kết nối với pin 9 Hải cảng VỚI. Để bật đèn LED này, chúng ta cần áp một logic vào chân cổng tương ứng để “thắp sáng” đèn LED.

Hãy thêm mã để thiết lập đầu ra cổng LED vào chương trình của chúng tôi, đồng thời thêm chức năng trì hoãn phần mềm để giảm tần số chuyển đổi đèn LED:

// Đừng quên kết nối tập tin tiêu đề với mô tả về các thanh ghi vi điều khiển

#include "stm32f10x.h"

trống rỗng Trì hoãn ( trống rỗng);

trống rỗng Trì hoãn ( trống rỗng)
{
dài không dấu Tôi;
(tôi=0; tôi<2000000; i++);
}

//Chức năng chính của chúng tôi

trống rỗng chủ yếu( trống rỗng)
{


RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

// xóa các bit MODE9 (đặt lại các bit MODE9_1 và MODE9_0 về 0)
GPIOC->CRH &= ~GPIO_CRH_MODE9;

// Đặt bit MODE9_1 để định cấu hình đầu ra đầu ra với tốc độ 2 MHz
GPIOC->CRH |= GPIO_CRH_MODE9_1;

// xóa các bit CNF (được đặt làm đầu ra cho mục đích chung, đối xứng (đẩy-kéo))
GPIOC->CRH &= ~GPIO_CRH_CNF9;

trong khi(1)
{

//Đặt chân 9 của cổng C thành chân logic (“đèn LED sáng”)
GPIOC->BSRR = GPIO_BSRR_BS9;


Trì hoãn();


GPIOC->BSRR = GPIO_BSRR_BR9;


Trì hoãn();

}
}

Bạn có thể tải xuống kho lưu trữ có mã nguồn của chương trình được viết bằng cách sử dụng điều khiển trực tiếp các thanh ghi vi điều khiển từ liên kết.

Chương trình khả thi đầu tiên của chúng tôi đã được viết; khi viết nó, để vận hành và định cấu hình các thiết bị ngoại vi, chúng tôi đã sử dụng dữ liệu từ bảng dữ liệu chính thức “ RM0041Thẩm quyền giải quyếtthủ công.pdf", nguồn thông tin về các thanh ghi vi điều khiển này là chính xác nhất nhưng để sử dụng được bạn phải đọc lại rất nhiều thông tin, điều này làm phức tạp việc viết chương trình. Để tạo điều kiện thuận lợi cho quá trình thiết lập các thiết bị ngoại vi của vi điều khiển, có nhiều trình tạo mã khác nhau, tiện ích chính thức từ công ty ST, chương trình Microxplorer được trình bày, nhưng nó vẫn có ít chức năng và vì lý do này, nó được tạo bởi các nhà phát triển bên thứ ba chương trình thay thế"Trình tạo mã chương trình STM32 » . Chương trình này cho phép bạn dễ dàng lấy mã cấu hình ngoại vi bằng giao diện đồ họa trực quan, thuận tiện (xem Hình 2).


Cơm. 2 Ảnh chụp màn hình của chương trình tạo mã STM32

Như có thể thấy trong Hình 2, mã cấu hình đầu ra LED do chương trình tạo ra trùng với mã chúng tôi đã viết trước đó.

Để chạy một chương trình viết sau khi biên dịch mã nguồn, chúng ta cần tải chương trình của mình vào bộ vi điều khiển và xem nó chạy như thế nào.

Video chế độ gỡ lỗi chương trình nhấp nháy LED

Video chương trình LED nhấp nháy trên board STM32VL Discovery

Chức năng thư viện để làm việc với các thiết bị ngoại vi

Để đơn giản hóa công việc thiết lập các thanh ghi ngoại vi của vi điều khiển, công ty ST đã phát triển các thư viện, nhờ sử dụng mà bạn không cần phải đọc kỹ datasheet, vì khi sử dụng các thư viện này, công việc viết chương trình sẽ trở nên đơn giản hơn. gần hơn với việc viết các chương trình cấp cao, vì thực tế là mọi chức năng cấp thấp đều được triển khai ở cấp chức năng thư viện. Tuy nhiên, không nên từ bỏ hoàn toàn việc sử dụng công việc trực tiếp với các thanh ghi vi điều khiển, vì các chức năng thư viện yêu cầu nhiều thời gian xử lý hơn để thực thi và do đó, việc sử dụng chúng trong các phần quan trọng về thời gian của chương trình là không hợp lý. Tuy nhiên, trong hầu hết các trường hợp, những việc như khởi tạo thiết bị ngoại vi không quan trọng đối với thời gian thực thi và sự thuận tiện của việc sử dụng các chức năng thư viện sẽ được ưu tiên hơn.

Bây giờ hãy viết chương trình của chúng ta bằng thư viện ST. Chương trình yêu cầu thiết lập cổng đầu vào/đầu ra để sử dụng các chức năng thư viện thiết lập cổng, bạn cần kết nối tệp tiêu đề " stm32f10x_gpio.h"(xem bảng. 1). Tệp này có thể được kết nối bằng cách bỏ ghi chú dòng tương ứng trong tệp cấu hình tiêu đề được kết nối " stm32f10x_conf.h" Ở cuối tập tin " stm32f10x_gpio.h» có danh sách khai báo hàm để làm việc với cổng. Bạn có thể tìm thấy mô tả chi tiết về tất cả các chức năng có sẵn trong tệp “ stm32f10x_stdperiph_lib_um.chm", mô tả ngắn gọn về những cái được sử dụng phổ biến nhất được đưa ra trong Bảng 2.

Bảng 2. Mô tả các chức năng cấu hình cổng chính

Chức năng

Mô tả hàm, các tham số được truyền và trả về

GPIO_DeInit(
GPIO_TypeDef* GPIOx)

Đặt các thanh ghi cấu hình cổng GPIOx về giá trị mặc định của chúng.

GPIO_Init(
GPIO_TypeDef* GPIOx,

Đặt các thanh ghi cấu hình cổng GPIOx theo các tham số đã chỉ định trong cấu trúc GPIO_InitStruct

GPIO_StructInit(
GPIO_InitTypeDef* GPIO_InitStruct)

Điền vào tất cả các trường của cấu trúc GPIO_InitStruct với các giá trị mặc định

uint8_t GPIO_ReadInputDataBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin);

Đọc giá trị đầu vào của chân GPIO_Pin của cổng GPIOx

uint16_t GPIO_ReadInputData (
GPIO_TypeDef* GPIOx)

Đọc giá trị đầu vào của tất cả các chân cổng GPIOx

GPIO_SetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Đặt giá trị đầu ra của chân GPIO_Pin của cổng GPIOx thành logic một

GPIO_ResetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Đặt lại giá trị đầu ra của chân GPIO_Pin của cổng GPIOx về logic 0

GPIO_WriteBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin,
BitAction BitVal)

Ghi giá trị BitVal vào chân GPIO_Pin của cổng GPIOx

GPIO_Write(
GPIO_TypeDef* GPIOx,
uint16_t PortVal)

Ghi giá trị PortVal vào cổng GPIOx

Như có thể thấy từ phần mô tả các chức năng, dưới dạng tham số cho cài đặt cổng, v.v., không có nhiều tham số riêng lẻ khác nhau được truyền cho hàm mà là một cấu trúc. Cấu trúc là dữ liệu kết hợp có một số mối quan hệ logic. Không giống như mảng, cấu trúc có thể chứa dữ liệu thuộc nhiều loại khác nhau. Nói cách khác, một cấu trúc đại diện cho một tập hợp các biến khác nhau với các loại khác nhau, được kết hợp thành một biến duy nhất. Các biến nằm trong cấu trúc này được gọi là các trường của cấu trúc và chúng được truy cập theo cách sau: đầu tiên viết tên cấu trúc, sau đó viết dấu chấm và tên trường của cấu trúc (tên biến trong cấu trúc này).

Danh sách các biến có trong cấu trúc của các hàm làm việc với cổng được mô tả trong cùng một tệp phía trên mô tả hàm một chút. Vì vậy, ví dụ, cấu trúc " GPIO_InitTypeDef"có cấu trúc sau:

cấu trúc typedef
{

uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
Tham số này có thể là bất kỳ giá trị nào của @ref GPIO_pins_define */

GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the tốc độ cho các chân đã chọn.
Tham số này có thể là giá trị của @ref GPIOSpeed_TypeDef */

GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
Tham số này có thể là giá trị của @ref GPIOMode_TypeDef */

)GPIO_InitTypeDef;

Trường đầu tiên của cấu trúc này chứa biến " GPIO_ Ghim" kiểu chưa ký ngắn, trong biến này, cần phải ghi lại cờ của số lượng các chân tương ứng mà các cài đặt cần thiết sẽ được thực hiện. Bạn có thể định cấu hình nhiều đầu ra cùng lúc bằng cách chỉ định một số hằng số làm tham số bằng toán tử theo bit HOẶC(cm. ). Bitwise OR sẽ "thu thập" tất cả những cái từ các hằng số được liệt kê và bản thân các hằng số chỉ là một mặt nạ dành cho mục đích sử dụng đó. Định nghĩa macro của các hằng số được liệt kê trong cùng một tệp bên dưới.

Trường thứ hai của cấu trúc " GPIO_InitTypeDef»đặt tốc độ tối đa có thể của đầu ra cổng. Danh sách các giá trị có thể của lĩnh vực này liệt kê ở trên:

Mô tả các giá trị có thể:

  • GPIO_Chế độ_AIN- đầu vào analog (tiếng Anh: Analog INput);
  • GPIO_Mode_IN_FLOATING- đầu vào không thắt chặt, lơ lửng (tiếng Anh: Phao đầu vào) trong không khí
  • GPIO_Chế độ_IPD- kéo xuống đầu vào
  • GPIO_Chế độ_IPU- kéo lên đầu vào
  • GPIO_Mode_Out_OD- Xả mở đầu ra
  • GPIO_Mode_Out_PP- đầu ra ở hai trạng thái (tiếng Anh: Output Push-Pull - qua lại)
  • GPIO_Chế độ_AF_OD- đầu ra cống mở cho các chức năng thay thế (Chức năng thay thế tiếng Anh). Được sử dụng trong trường hợp đầu ra phải được điều khiển bởi các thiết bị ngoại vi gắn liền với kết luận này cổng (ví dụ: chân Tx USART1, v.v.)
  • GPIO_Chế độ_AF_PP- điều tương tự, nhưng với hai trạng thái

Theo cách tương tự, bạn có thể xem cấu trúc các biến của các cấu trúc khác cần thiết để làm việc với các hàm thư viện.

Để làm việc với các cấu trúc, chúng, giống như các biến, phải được khai báo và gán cho chúng. tên duy nhất, sau đó bạn có thể truy cập các trường của cấu trúc được khai báo theo tên được gán cho nó.

//Khai báo cấu trúc

/*
Trước khi bạn bắt đầu điền vào các trường của cấu trúc, bạn nên khởi tạo nội dung của cấu trúc bằng dữ liệu mặc định; việc này được thực hiện để tránh ghi dữ liệu không chính xác nếu vì lý do nào đó, không phải tất cả các trường của cấu trúc đều được điền vào. .

Để truyền các giá trị của cấu trúc cho hàm, bạn phải đặt trước tên cấu trúc bằng ký hiệu &. Biểu tượng này cho trình biên dịch biết rằng cần phải truyền vào hàm không phải các giá trị có trong cấu trúc mà là địa chỉ trong bộ nhớ nơi chứa các giá trị này. Điều này được thực hiện nhằm giảm số lượng hành động cần thiết của bộ xử lý để sao chép nội dung của cấu trúc và cũng cho phép lưu ĐẬP. Do đó, thay vì truyền nhiều byte có trong cấu trúc cho hàm, chỉ một byte chứa địa chỉ của cấu trúc sẽ được truyền.
*/

/* Viết vào trường GPIO_Pin của cấu trúc GPIO_Init_struct số pin của cổng mà chúng ta sẽ cấu hình thêm */

GPIO_Init_struct.GPIO_Pin=GPIO_Pin_9;

/* Điền vào trường GPIO_Speed ​​​​theo cách tương tự */

/*
Sau khi chúng ta điền vào các trường cần thiết của cấu trúc, cấu trúc này phải được chuyển đến một hàm sẽ thực hiện mục nhập cần thiết vào các thanh ghi thích hợp. Ngoài cấu trúc có các cài đặt cho chức năng này, cũng cần phải chuyển tên của cổng mà các cài đặt dự định.
*/

Hầu hết tất cả các thiết bị ngoại vi đều được cấu hình theo cách gần giống nhau; điểm khác biệt duy nhất là ở các tham số và lệnh cụ thể cho từng thiết bị.

Bây giờ hãy viết chương trình nhấp nháy đèn LED chỉ sử dụng các chức năng thư viện.

//Đừng quên bao gồm tệp tiêu đề với mô tả về các thanh ghi vi điều khiển

#include "stm32f10x.h"
#include "stm32f10x_conf.h"

//khai báo hàm delay của phần mềm

trống rỗng Trì hoãn ( trống rỗng);

// chính chức năng trì hoãn phần mềm

trống rỗng Trì hoãn ( trống rỗng)
{
dài không dấu Tôi;
(tôi=0; tôi<2000000; i++);
}

//Chức năng chính của chúng tôi

trống rỗng chủ yếu( trống rỗng)
{

//Cho phép tính giờ bus cổng C
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Khai báo cấu trúc để cấu hình cổng
GPIO_InitTypeDef GPIO_Init_struct;

// Điền vào cấu trúc với các giá trị ban đầu
GPIO_StructInit(&GPIO_Init_struct);

/* Viết vào trường GPIO_Pin của cấu trúc GPIO_Init_struct số pin của cổng mà chúng ta sẽ cấu hình thêm */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Điền vào trường GPIO_Speed ​​và GPIO_Mode theo cách tương tự
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

// Truyền cấu trúc đã điền để thực hiện các hành động cấu hình các thanh ghi
GPIO_Init(GPIOC, &GPIO_Init_struct);

//Vòng lặp vô tận chính của chúng ta
trong khi(1)
{
// Đặt chân 9 của cổng C thành chân logic (đèn LED sáng)
GPIO_SetBits(GPIOC, GPIO_Pin_9);

//Thêm độ trễ phần mềm để đèn LED phát sáng một lúc
Trì hoãn();

// Đặt lại trạng thái chân 9 của cổng C về mức logic 0
GPIO_ResetBits(GPIOC, GPIO_Pin_9);

//Thêm độ trễ phần mềm một lần nữa
Trì hoãn();
}
}

liên kết.

Từ ví dụ trên, rõ ràng rằng việc sử dụng các hàm thư viện để làm việc với thiết bị ngoại vi cho phép bạn đưa chương trình viết cho vi điều khiển đến gần hơn với lập trình hướng đối tượng và cũng làm giảm nhu cầu truy cập thường xuyên vào biểu dữ liệu để đọc mô tả về vi điều khiển. đăng ký, nhưng việc sử dụng các chức năng thư viện đòi hỏi kiến ​​thức cao hơn về ngôn ngữ lập trình. Theo quan điểm này, đối với những người không đặc biệt quen thuộc với lập trình, một lựa chọn đơn giản hơn để viết chương trình sẽ là cách viết chương trình mà không cần sử dụng các chức năng thư viện, có quyền truy cập trực tiếp vào các thanh ghi của vi điều khiển. Đối với những người biết rõ ngôn ngữ lập trình nhưng không thành thạo về vi điều khiển, đặc biệt là STM32, việc sử dụng các hàm thư viện giúp đơn giản hóa đáng kể quá trình viết chương trình.

Tình huống này, cũng như việc ST đã quan tâm đến mức độ tương thích cao, cả về phần cứng và phần mềm, của các bộ vi điều khiển khác nhau của nó, giúp việc nghiên cứu chúng dễ dàng hơn vì không cần phải đi sâu vào các đặc điểm cấu trúc của nhiều bộ điều khiển khác nhau dòng STM32 và cho phép bạn chọn bất kỳ bộ vi điều khiển nào có sẵn trong dòng STM32 làm bộ vi điều khiển để nghiên cứu.

xử lý ngắt

Bộ vi điều khiển có một khả năng đáng chú ý - dừng việc thực thi chương trình chính cho một sự kiện cụ thể và tiến hành thực hiện một chương trình con đặc biệt - xử lý ngắt. Các nguồn ngắt có thể là các sự kiện bên ngoài - gián đoạn nhận/truyền dữ liệu qua bất kỳ giao diện truyền dữ liệu nào hoặc thay đổi trạng thái đầu ra hoặc các sự kiện bên trong - tràn bộ đếm thời gian, v.v. Danh sách các nguồn ngắt có thể có cho bộ vi điều khiển dòng STM32 được đưa ra trong biểu dữ liệu " RM0041 Tài liệu tham khảo" Trong chuong " 8 Sự gián đoạn và sự kiện».

Vì trình xử lý ngắt cũng là một hàm nên nó sẽ được viết như một hàm thông thường, nhưng để trình biên dịch biết rằng hàm này là một trình xử lý ngắt cụ thể, các tên được xác định trước phải được chọn làm tên hàm, để chuyển hướng vectơ ngắt. Được xác định. Danh sách tên của các hàm này với mô tả ngắn gọn nằm trong tệp trình biên dịch mã " startup_stm32f10x_md_vl.s" Một trình xử lý ngắt có thể có nhiều nguồn gây ngắt, ví dụ như hàm xử lý ngắt " USART1_IRQHTrình xử lý"có thể được gọi khi kết thúc nhận và kết thúc gửi một byte, v.v.

Để bắt đầu làm việc với các ngắt, bạn phải cấu hình và khởi tạo bộ điều khiển ngắt NVIC. Trong kiến ​​trúc Cortex M3, mỗi ngắt có thể được chỉ định nhóm ưu tiên riêng trong trường hợp có nhiều ngắt xảy ra đồng thời. Sau đó, bạn cần cấu hình nguồn ngắt.

Trường NVIC_IRQChannel cho biết chúng ta muốn cấu hình ngắt nào. Hằng số USART1_IRQn biểu thị kênh chịu trách nhiệm về các ngắt liên quan đến USART1. Nó được định nghĩa trong tập tin " stm32f10x.h", các hằng số tương tự khác cũng được xác định ở đó.

Hai trường tiếp theo cho biết mức độ ưu tiên ngắt (giá trị tối đa cho hai tham số này được xác định bởi nhóm ưu tiên đã chọn). Trường cuối cùng thực sự cho phép sử dụng ngắt.

Để hoạt động NVIC_Init, giống như khi thiết lập cổng, một con trỏ tới một cấu trúc được truyền để áp dụng các cài đặt đã thực hiện và ghi chúng vào các thanh ghi tương ứng của bộ vi điều khiển.

Bây giờ trong cài đặt mô-đun, bạn cần đặt các tham số mà mô-đun này sẽ tạo ra ngắt. Đầu tiên bạn cần kích hoạt ngắt; việc này được thực hiện bằng cách gọi hàm tên_ITConfig(), nằm trong tệp tiêu đề thiết bị ngoại vi.

//Cho phép ngắt sau khi hoàn thành truyền byte qua USART1
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);

//Cho phép ngắt khi nhận được byte qua USART1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

Bạn có thể tìm thấy mô tả về các tham số được truyền cho hàm trong tệp mã nguồn của thiết bị ngoại vi, ngay phía trên vị trí của chính hàm đó. Chức năng này cho phép hoặc vô hiệu hóa các ngắt đối với các sự kiện khác nhau từ mô-đun ngoại vi được chỉ định. Khi chức năng này được thực thi, bộ vi điều khiển sẽ có thể tạo ra các ngắt cho các sự kiện mà chúng ta yêu cầu.

Sau khi vào chức năng xử lý ngắt, chúng ta cần kiểm tra xem sự kiện nào đã xảy ra ngắt, sau đó đặt lại cờ, nếu không, khi thoát khỏi ngắt, bộ vi điều khiển sẽ quyết định rằng chúng ta không xử lý ngắt, vì cờ ngắt là vẫn được thiết lập.

Để thực hiện các hành động nhỏ, lặp đi lặp lại với khoảng thời gian chính xác, bộ vi điều khiển có lõi Cortex-M3 có bộ hẹn giờ hệ thống được thiết kế đặc biệt cho việc này. Các chức năng của bộ hẹn giờ này chỉ bao gồm việc gọi ngắt theo những khoảng thời gian được chỉ định nghiêm ngặt. Thông thường, ngắt được gọi bởi bộ đếm thời gian này chứa mã để đo thời lượng của các quy trình khác nhau. Phần khai báo chức năng cài đặt hẹn giờ nằm ​​trong file " cốt lõi_ cmt3. h" Đối số được truyền cho hàm chỉ định số chu kỳ xung nhịp bus hệ thống giữa các khoảng thời gian gọi bộ xử lý ngắt bộ hẹn giờ hệ thống.

SysTick_Config(clk);

Bây giờ chúng ta đã xử lý xong các ngắt, hãy viết lại chương trình của chúng ta bằng cách sử dụng bộ đếm thời gian hệ thống làm thành phần định thời gian. Kể từ khi đồng hồ bấm giờ " SysTick” là một hệ thống và có thể được sử dụng bởi nhiều khối chức năng khác nhau trong chương trình của chúng ta, khi đó sẽ hợp lý nếu chuyển chức năng xử lý ngắt bộ đếm thời gian hệ thống thành một tệp riêng biệt và từ hàm này gọi các chức năng cho từng khối chức năng riêng biệt.

Một ví dụ về tệp “main.c” cho chương trình nhấp nháy LED bằng cách sử dụng ngắt:

//Kết nối tệp tiêu đề với mô tả về các thanh ghi vi điều khiển

#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "main.h"

int không dấu LED_timer;

//Hàm được gọi từ hàm xử lý ngắt bộ đếm thời gian hệ thống

trống rỗng SysTick_Timer_main( trống rỗng)
{
//Nếu biến LED_timer chưa đạt 0,
nếu như(LED_hẹn giờ)
{
// Kiểm tra giá trị của nó, nếu lớn hơn 1500 thì bật LED
nếu như(LED_timer>1500) GPIOC->BSRR= GPIO_BSRR_BS9;

// ngược lại nếu nhỏ hơn hoặc bằng 1500 thì tắt đi
khác GPIOC->BSRR= GPIO_BSRR_BR9;

//Giảm biến LED_timer
LED_timer--;
}

// Nếu giá trị của biến bằng 0, đặt giá trị mới là 2000
khác LED_timer=2000;
}

//Chức năng chính của chúng tôi

trống rỗng chủ yếu( trống rỗng)
{

//Cho phép tính giờ bus cổng C
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Khai báo cấu trúc để cấu hình cổng
GPIO_InitTypeDef GPIO_Init_struct;

// Điền vào cấu trúc với các giá trị ban đầu
GPIO_StructInit(&GPIO_Init_struct);

/* Viết vào trường GPIO_Pin của cấu trúc GPIO_Init_struct số pin của cổng mà chúng ta sẽ cấu hình thêm */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Điền vào trường GPIO_Speed ​​và GPIO_Mode theo cách tương tự
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

// Truyền cấu trúc đã điền để thực hiện các hành động cấu hình các thanh ghi
GPIO_Init(GPIOC, &GPIO_Init_struct);

//chọn nhóm ưu tiên cho các ngắt
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

// Định cấu hình bộ đếm thời gian hệ thống với khoảng thời gian 1ms
SysTick_Config(24000000/1000);

//Vòng lặp vô tận chính của chúng ta
trong khi(1)
{
// Lần này nó trống, tất cả điều khiển LED xảy ra khi bị gián đoạn
}
}

Một phần mã nguồn trong file “stm32f10x_it.c”:


#include "main.h"

/**
* @brief Hàm này xử lý SysTick Handler.
* @param Không có
* @retval Không có
*/

trống rỗng SysTick_Handler( trống rỗng)
{
SysTick_Timer_main();
}

Bạn có thể tải xuống ví dụ về bản nháp đang hoạt động của chương trình nhấp nháy đèn LED bằng cách sử dụng ngắt từ liên kết.

Điều này kết thúc câu chuyện của tôi về những điều cơ bản trong việc phát triển chương trình cho bộ vi điều khiển STM32. Tôi đã cung cấp tất cả thông tin cần thiết để kích hoạt thêm tự học Bộ vi điều khiển STM32. Tài liệu được cung cấp chỉ là phần mở đầu, vì không thể mô tả mô tả đầy đủ về cách làm việc với bộ vi điều khiển trong khuôn khổ của bất kỳ bài viết nào. Ngoài ra, việc nghiên cứu bộ vi điều khiển mà không có kinh nghiệm thực tế là không thể, và kinh nghiệm thực tế sẽ dần dần có được sau nhiều năm làm việc, thử nghiệm, tích lũy nhiều phát triển phần mềm và phần cứng khác nhau, cũng như đọc nhiều bài báo và tài liệu khác nhau về bộ vi điều khiển. Nhưng đừng để điều này làm bạn sợ, vì thông tin được cung cấp trong bài viết khá đủ để tạo thiết bị đầu tiên của bạn trên bộ vi điều khiển và bạn có thể tự mình tiếp thu thêm kiến ​​​​thức và kinh nghiệm, phát triển ngày càng phức tạp và phức tạp hơn. thiết bị tốt nhất và cải thiện kỹ năng của bạn.

Tôi hy vọng tôi có thể khiến bạn quan tâm đến việc nghiên cứu bộ vi điều khiển và phát triển các thiết bị dựa trên chúng, đồng thời công việc của tôi sẽ hữu ích và thú vị với bạn.

Khi bạn mới bắt đầu lập trình vi điều khiển hoặc đã lâu chưa lập trình, việc hiểu được code của người khác là điều không dễ dàng. Câu hỏi "Đây là gì?" và "Cái này đến từ đâu?" xuất hiện trên hầu hết mọi sự kết hợp của chữ cái và số. Và việc hiểu logic “cái gì? tại sao? và ở đâu?” càng nhanh thì việc nghiên cứu mã của người khác càng dễ dàng, bao gồm cả các ví dụ. Đúng vậy, đôi khi để làm được điều này, bạn phải “nhảy qua mã” và “xem qua hướng dẫn sử dụng” trong hơn một ngày.

Tất cả các bộ vi điều khiển STM32F4xx đều có khá nhiều thiết bị ngoại vi. Mỗi thiết bị ngoại vi của bộ vi điều khiển được gán một vùng bộ nhớ cụ thể, cụ thể và không thể định vị lại. Mỗi vùng bộ nhớ bao gồm các thanh ghi bộ nhớ và các thanh ghi này có thể là 8 bit, 16 bit, 32 bit hoặc loại khác, tùy thuộc vào bộ vi điều khiển. Trong bộ vi điều khiển STM32F4, các thanh ghi này là 32 bit và mỗi thanh ghi có mục đích riêng và địa chỉ cụ thể riêng. Không có gì ngăn cản bạn truy cập chúng trực tiếp trong chương trình của bạn bằng cách chỉ ra địa chỉ. Thanh ghi này hoặc thanh ghi kia được đặt tại địa chỉ nào và nó thuộc về thiết bị ngoại vi nào được ghi trong thẻ nhớ. Đối với STM32F4, thẻ nhớ như vậy có trong tài liệu DM00031020.pdf, có thể tìm thấy trên st.com. Tài liệu đó được gọi là

RM0090
Hướng dẫn tham khảo
STM32F405xx/07xx, STM32F415xx/17xx, STM32F42xxx và STM32F43xxx MCU 32-bit dựa trên ARM tiên tiến

Trong chuong 2.3 Bản đồ bộ nhớở trang 64, một bảng bắt đầu bằng địa chỉ của các khu vực đăng ký và mối liên kết của chúng với thiết bị ngoại vi. Trong cùng một bảng có một liên kết đến một phần phân bổ bộ nhớ chi tiết hơn cho từng thiết bị ngoại vi.

Bảng bên trái hiển thị phạm vi địa chỉ, ở giữa là tên của thiết bị ngoại vi và ở cột cuối cùng là nơi mô tả chi tiết hơn về phân bổ bộ nhớ.

Vì vậy, đối với các cổng I/O mục đích chung GPIO trong bảng cấp phát bộ nhớ, bạn có thể thấy rằng các địa chỉ được phân bổ cho chúng bắt đầu từ 0x4002 0000. Cổng I/O mục đích chung GPIOA chiếm phạm vi địa chỉ từ 0x4002 000 đến 0x4002 03FF. Cổng GPIOB chiếm dải địa chỉ 0x4002 400 - 0x4002 07FF. Và như thế.

Để xem thêm phân phối chi tiết trong phạm vi đó, bạn chỉ cần theo liên kết.

Ở đây cũng có một bảng nhưng có bản đồ bộ nhớ cho dải địa chỉ GPIO. Theo bản đồ bộ nhớ này, 4 byte đầu tiên thuộc về thanh ghi MODER, 4 byte tiếp theo thuộc về thanh ghi OTYPER, v.v. Địa chỉ đăng ký được tính từ đầu phạm vi thuộc về một cổng GPIO cụ thể. Nghĩa là, mỗi thanh ghi GPIO có một địa chỉ cụ thể có thể được sử dụng khi phát triển chương trình cho vi điều khiển.

Nhưng việc sử dụng địa chỉ đăng ký là bất tiện và gây khó khăn cho con người một lượng lớn lỗi. Do đó, các nhà sản xuất bộ vi điều khiển tạo ra các thư viện tiêu chuẩn giúp làm việc với bộ vi điều khiển dễ dàng hơn. Trong các thư viện này, địa chỉ vật lý được khớp với ký hiệu chữ cái. Đối với STM32F4xx, các tương ứng này được chỉ định trong tệp stm32f4xx.h. Tài liệu stm32f4xx.h thuộc về thư viện CMSIS và nằm trong thư mục Libraries\CMSIS\ST\STM32F4xx\Include\.

Hãy xem cổng GPIOA được xác định như thế nào trong các thư viện. Mọi thứ khác được xác định tương tự. Chỉ cần hiểu nguyên tắc là đủ. Tài liệu stm32f4xx.h khá lớn và do đó tốt hơn nên sử dụng tìm kiếm hoặc các khả năng mà chuỗi công cụ của bạn cung cấp.

Đối với cổng GPIOA, chúng tôi tìm thấy dòng đề cập đến GPIOA_BASE

GPIOA_BASE được xác định thông qua AHB1PERIPH_BASE

AHB1PERIPH_BASE lần lượt được xác định thông qua PERIPH_BASE

Và lần lượt, PERIPH_BASE được định nghĩa là 0x4000 0000. Nếu bạn nhìn vào bản đồ phân bổ bộ nhớ của các thiết bị ngoại vi (trong phần 2.3 Bản đồ bộ nhớở trang 64), chúng ta sẽ thấy địa chỉ này ở cuối bảng. Các thanh ghi của tất cả các thiết bị ngoại vi của bộ vi điều khiển STM32F4 đều bắt đầu từ địa chỉ này. Tức là PERIPH_BASE là địa chỉ bắt đầu của toàn bộ ngoại vi của bộ vi điều khiển STM32F4xx nói chung và bộ vi điều khiển STM32F407VG nói riêng.

AHB1PERIPH_BASE được định nghĩa là tổng của (PERIPH_BASE + 0x00020000). (xem hình ảnh phía sau). Đây sẽ là địa chỉ 0x4002 0000. Trong thẻ nhớ, các cổng đầu vào/đầu ra GPIO cho mục đích chung bắt đầu tại địa chỉ này.

GPIOA_BASE được định nghĩa là (AHB1PERIPH_BASE + 0x0000), nghĩa là nó là địa chỉ bắt đầu của nhóm thanh ghi cổng GPIOA.

Chà, bản thân cổng GPIOA được định nghĩa là cấu trúc của các thanh ghi, vị trí của chúng trong bộ nhớ bắt đầu bằng địa chỉ GPIOA_BASE (xem dòng #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE).

Cấu trúc của mỗi cổng GPIO được định nghĩa là GPIO_TypeDef.

Vì vậy, các thư viện tiêu chuẩn, trong trường hợp này là tập tin stm32f4xx.h, chỉ đơn giản là nhân bản hóa địa chỉ máy. Nếu bạn thấy mục GPIOA->ODR = 1234, thì điều này có nghĩa là số 1234 sẽ được ghi vào địa chỉ 0x40020014. GPIOA có địa chỉ bắt đầu là 0x40020000 và thanh ghi ODR có địa chỉ 0x14 tính từ đầu phạm vi, vì vậy GPIOA->ODR có địa chỉ 0x40020014.

Hoặc, ví dụ: bạn không thích mục GPIOA->ODR, thì bạn có thể xác định #define GPIOA_ODR ((uint32_t *) 0x40020014) và nhận được kết quả tương tự bằng cách viết GPIOA_ODR = 1234;. Nhưng điều này có ích lợi thế nào? Nếu bạn thực sự muốn giới thiệu các ký hiệu của riêng mình, thì tốt hơn là bạn chỉ cần gán lại các ký hiệu tiêu chuẩn. Bạn có thể xem cách thực hiện việc này trong tệp stm32f4_discovery.h Ví dụ: đây là cách xác định một trong các đèn LED ở đó:

#define LED4_PIN GPIO_Pin_12
#define LED4_GPIO_PORT GPIOD
#define LED4_GPIO_CLK RCC_AHB1Periph_GPIOD

Một mô tả chi tiết hơn về ngoại vi cổng có thể được tìm thấy trong stm32f4xx_gpio.h

Cho đến thời điểm này chúng tôi đã sử dụng thư viện chuẩn hạt nhân - CMSIS. Để cấu hình bất kỳ cổng nào trên chế độ mong muốn làm việc, chúng tôi phải tìm đến sổ đăng ký chịu trách nhiệm cho một chức năng nhất định, đồng thời tìm kiếm trong một tài liệu lớn để biết các thông tin khác liên quan đến quá trình này. Mọi thứ sẽ còn phức tạp và thường xuyên hơn khi chúng ta bắt đầu làm việc với bộ đếm thời gian hoặc ADC. Số lượng thanh ghi ở đó lớn hơn nhiều so với số lượng cổng I/O. Cài đặt thủ công mất nhiều thời gian và tăng nguy cơ mắc sai lầm. Vì vậy, nhiều người thích làm việc với thư viện ngoại vi tiêu chuẩn - StdPeriph. Nó cho cái gì? Thật đơn giản - mức độ trừu tượng tăng lên, bạn không cần phải đi sâu vào tài liệu và suy nghĩ về các thanh ghi trong hầu hết các phần. Trong thư viện này, tất cả các chế độ hoạt động và thông số của ngoại vi MK đều được mô tả dưới dạng cấu trúc. Bây giờ, để định cấu hình một thiết bị ngoại vi, bạn chỉ cần gọi hàm khởi tạo thiết bị với cấu trúc đã điền.

Dưới đây là hình ảnh minh họa sơ đồ các mức độ trừu tượng.

Chúng tôi đã làm việc với CMSIS (nơi "gần" nhất với lõi) để hiển thị cách hoạt động của bộ vi điều khiển. Bước tiếp theo là thư viện chuẩn mà chúng ta sẽ học cách sử dụng ngay bây giờ. Tiếp theo là trình điều khiển thiết bị. Chúng được hiểu là các tệp *.c \ *.h mang lại sự tiện lợi giao diện phần mềmđể điều khiển bất kỳ thiết bị nào. Ví dụ: trong khóa học này, chúng tôi sẽ cung cấp cho bạn trình điều khiển cho chip max7219 và mô-đun WiFi Esp8266.

Một dự án tiêu chuẩn sẽ bao gồm tập tin sau:


Tất nhiên, trước tiên, đây là các tệp CMSIS cho phép thư viện chuẩn hoạt động với kernel, chúng ta đã nói về chúng rồi. Thứ hai, các tập tin thư viện tiêu chuẩn. Và thứ ba, tập tin người dùng.

Các tập tin thư viện có thể được tìm thấy trên trang dành riêng cho MK mục tiêu (đối với chúng tôi đó là stm32f10x4), trong phần Tài nguyên thiết kế(trong CooCox IDE, các tệp này được tải xuống từ kho lưu trữ của môi trường phát triển). Mỗi thiết bị ngoại vi tương ứng với hai tệp - tiêu đề (*.h) và mã nguồn (*.c). Bạn có thể tìm thấy mô tả chi tiết trong tệp hỗ trợ, nằm trong kho lưu trữ cùng với thư viện trên trang web.

  • stm32f10x_conf.h - tệp cấu hình thư viện. Người dùng có thể kết nối hoặc ngắt kết nối các mô-đun.
  • stm32f10x_ppp.h - tệp tiêu đề ngoại vi. Thay vì ppp có thể có gpio hoặc adc.
  • stm32f10x_ppp.c - trình điều khiển thiết bị ngoại vi được viết bằng ngôn ngữ C.
  • stm32f10x_it.h - tệp tiêu đề bao gồm tất cả các trình xử lý ngắt có thể có (nguyên mẫu của chúng).
  • stm32f10x_it.c là tệp mã nguồn mẫu chứa thường trình dịch vụ ngắt (ISR) dành cho các tình huống ngoại lệ trong Cortex M3. Người dùng có thể thêm ISR của riêng mình cho các thiết bị ngoại vi được sử dụng.

Thư viện tiêu chuẩn và các thiết bị ngoại vi có quy ước về cách đặt tên hàm và ký hiệu.

  • PPP là từ viết tắt của các thiết bị ngoại vi, chẳng hạn như ADC.
  • Các tệp hệ thống, tiêu đề và mã nguồn - bắt đầu bằng stm32f10x_.
  • Các hằng số được sử dụng trong một tệp được xác định trong tệp đó. Các hằng số được sử dụng trong nhiều tệp được xác định trong tệp tiêu đề. Tất cả các hằng số trong thư viện ngoại vi thường được viết bằng chữ UPPER.
  • Các thanh ghi được coi là hằng số và còn được gọi là chữ cái VỐN.
  • Tên hàm dành riêng cho thiết bị ngoại vi bao gồm từ viết tắt, chẳng hạn như USART_SendData() .
  • Để định cấu hình từng thiết bị ngoại vi, cấu trúc PPP_InitTypeDef được sử dụng và được chuyển đến hàm PPP_Init().
  • Để khởi tạo lại (đặt giá trị thành mặc định), bạn có thể sử dụng hàm PPP_DeInit().
  • Chức năng cho phép bạn bật hoặc tắt các thiết bị ngoại vi được gọi là PPP_Cmd().
  • Chức năng bật/tắt ngắt được gọi là PPP_ITConfig.

VỚI danh sách đầy đủ bạn có thể xem lại tệp hỗ trợ thư viện. Bây giờ hãy viết lại đèn LED nhấp nháy bằng thư viện ngoại vi tiêu chuẩn!

Trước khi bắt đầu công việc, chúng ta hãy xem file stm32f10x.h và tìm dòng:

#xác định USE_STDPERIPH_DRIVER

Nếu bạn định cấu hình dự án từ đầu bằng cách sử dụng các tệp thư viện từ kho lưu trữ đã tải xuống, thì bạn sẽ cần bỏ ghi chú dòng này. Nó sẽ cho phép bạn sử dụng thư viện tiêu chuẩn. Định nghĩa này (macro) sẽ ra lệnh cho bộ tiền xử lý bao gồm tệp stm32f10x_conf.h:

#ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif

Tập tin này chứa các mô-đun. Nếu bạn chỉ cần những cái cụ thể, hãy tắt những cái còn lại, điều này sẽ tiết kiệm thời gian trong quá trình biên dịch. Chúng tôi, như bạn có thể đoán, cần mô-đun RTC và GPIO (tuy nhiên, trong tương lai bạn cũng sẽ cần _bkp.h , _flash , _pwr.h , _rtc.h , _spi.h , _tim.h , _usart.h ):

#include "stm32f10x_flash.h" // cho init_pll() #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h"

Giống như lần trước, trước tiên bạn cần kích hoạt tính năng xung nhịp của cổng B. Việc này được thực hiện bằng hàm được khai báo trong stm32f10x_rcc.h:

Void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, Trạng thái chức năng NewState);

Enum FunctionState được định nghĩa trong stm32f10x.h:

Typedef enum (DISABLE = 0, ENABLE = !DISABLE) Trạng thái chức năng;

Hãy khai báo cấu trúc để thiết lập nhánh của chúng ta (bạn có thể tìm thấy nó trong tệp stm32f10x_gpio.h):

Đèn LED GPIO_InitTypeDef;

Bây giờ chúng ta phải điền vào nó. Chúng ta hãy xem nội dung của cấu trúc này:

Cấu trúc Typedef ( uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; ) GPIO_InitTypeDef;

Tất cả các bảng liệt kê và hằng số cần thiết có thể được tìm thấy trong cùng một tệp. Khi đó hàm init_leds() được viết lại sẽ có dạng sau:

Void led_init() ( // Kích hoạt xung nhịp RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Khai báo cấu trúc và điền vào nó GPIO_InitTypeDef LED; LED.GPIO_Pin = GPIO_Pin_0; LED.GPIO_Speed ​​​= GPIO_Speed_2MHz; LED.GPIO_Mode = GPIO_Mode _Out_PP; // Khởi tạo cổng GPIO_Init( GPIOB,&LED);

Hãy viết lại hàm main():

Int main(void) ( led_init(); while (1) ( GPIO_SetBits(GPIOB, GPIO_Pin_0); delay(10000000); GPIO_ResetBits(GPIOB, GPIO_Pin_0); delay(10000000); ) )

Việc chính là cảm nhận thứ tự khởi tạo: bật đồng hồ ngoại vi, khai báo cấu trúc, điền cấu trúc, gọi phương thức khởi tạo. Các thiết bị ngoại vi khác thường được cấu hình theo cách tương tự.

Một lần nữa tôi muốn viết về sự khởi đầu đơn giản với STM32, chỉ lần này mà không sử dụng mẫu hoặc ví dụ của bất kỳ ai - kèm theo giải thích từng bước. Các bài viết sẽ được đánh số bước liên tục.

1. Cài đặt IAR

Xây dựng dự án trong IAR

1. Bộ tiền xử lý

  1. xóa bình luận

2. Trình biên dịch

3. Trình liên kết

3. Tạo dự án mới trong IAR

Sau khi khởi chạy IAR, một cửa sổ xuất hiện Trung tâm Thông tin, mà chúng tôi không cần. Nhấp vào menu Dự án -> Tạo dự án mới. Chọn chuỗi công cụ: ARM (có thể bạn sẽ không có bất kỳ thứ gì khác trong danh sách đó), Mẫu dự án: C -> main.

Trong cửa sổ bên trái (“Workspace”), nhấp chuột phải để mở menu và tạo nhóm mới(Thêm ->

Nhấp chuột phải vào CMSIS

Tới nhóm Khởi động

Chúng ta đã hoàn tất CMSIS.

Tới nhóm StdPeriphLib

Tới nhóm Người dùng

5. Thiết lập dự án

  1. Tùy chọn chung -> Mục tiêu ->
Chọn ST -> STM32F100 -> ST STM32F100xB. Đây là bộ điều khiển của chúng tôi. 2. Tùy chọn chung -> Cấu hình thư viện -> CMSIS: chọn hộp Sử dụng CMSIS. Vì vậy chúng ta sẽ sử dụng thư viện CMSIS được tích hợp sẵn trong trình biên dịch. Kể từ phiên bản 6.30, IAR bắt đầu được cung cấp CMSIS tích hợp và điều này có vẻ tốt hơn - nhưng nó gây ra một số nhầm lẫn với các dự án cũ hơn. 3. Trình biên dịch C/C++ ->
$PROJ_DIR$\

* Debugger –> Setup –> Driver: chọn ST–Link, vì đây là bộ lập trình được tích hợp sẵn trong bảng Discovery. Bây giờ chúng ta tự cấu hình bộ lập trình: * Trình gỡ lỗi -> ST–LINK -> Giao diện: chọn SWD (bộ lập trình trên bo mạch được kết nối với bộ điều khiển qua SWD, không qua JTAG). * Trình gỡ lỗi ->
#include "stm32f10x_conf.h" 

khoảng trống chính()
{
trong khi(1)
{
}
}

<1000000; i++);


vì(i=0; tôi<1000000; i++);

#include "stm32f10x_conf.h"

khoảng trống chính()
{





int tôi;
trong khi(1)
{

vì(i=0; tôi<1000000; i++);

<1000000; i++); } }

lưu trữ với dự án GPIO. May mắn thay, bạn có thể lưu dự án này và sử dụng nó làm mẫu để không phải thực hiện lại toàn bộ quá trình thiết lập. Toàn bộ chu trình: 1. Cổng I/O (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - từ 0 đến RTOS. 2: Hẹn giờ và ngắt") (/index.php/stm32-from_zero_to_rtos-3_timer_outputs/ " STM32 - từ đầu đến RTOS. 3: Đầu ra bộ hẹn giờ") [Một lần nữa tôi muốn viết về sự khởi đầu đơn giản với STM32, chỉ lần này mà không sử dụng mẫu hoặc ví dụ của bất kỳ ai - kèm theo lời giải thích về từng bước. Các bài viết sẽ được đánh số bước liên tục.

0. Chúng tôi trích xuất bảng STM32VLDiscovery

Chúng tôi mua nó ở cửa hàng, nó có giá 600 rúp. Bạn sẽ cần cài đặt trình điều khiển trên bo mạch - tôi nghĩ điều này sẽ không gây ra bất kỳ khó khăn nào.

1. Cài đặt IAR

Chúng tôi sẽ làm việc trong IAR - một IDE tốt với trình biên dịch xuất sắc. Nó thiếu sự thuận tiện khi viết mã - nhưng đối với mục đích của chúng tôi thì nó khá đủ. Tôi sử dụng IAR phiên bản 6.50.3, bạn biết lấy nó ở đâu.

2. Tải thư viện ngoại vi

Tôi không phải là người thích làm việc với các thanh ghi trong giai đoạn học tập. Do đó, tôi khuyên bạn nên tải xuống thư viện ngoại vi từ ST để có được các chức năng thuận tiện cho việc truy cập tất cả các cài đặt cần thiết.

Tạo một thư mục “STM32_Projects”, đặt thư mục Thư viện vào đó từ kho lưu trữ đã tải xuống (stsw-stm32078.zip/an3268/stm32vldiscovery_package), nó chứa CMSIS (thư viện từ ARM cho tất cả các bộ vi điều khiển Cortex, mô tả và địa chỉ của tất cả các tài nguyên) và STM32F10x_StdPeriph_Driver - một thư viện ngoại vi từ ST với tất cả các tính năng.

Chúng tôi cũng tạo một thư mục ở đó “1. GPIO”, đây sẽ là dự án đầu tiên của chúng tôi.

Cây thư mục được hiển thị trong hình. Hãy làm theo cách này nhé, vì sau này các đường dẫn tương đối trong cây này sẽ rất quan trọng.

Chà, để hiểu những gì chúng ta đang nói đến, hãy tải xuống tài liệu 1100 trang về các bộ điều khiển này.

Xây dựng dự án trong IAR

Cần phải hiểu rõ bản chất của quá trình lắp ráp dự án. Để thuận tiện, chúng tôi sẽ chia nó thành các giai đoạn.

1. Bộ tiền xử lý

Bộ tiền xử lý đi qua tất cả các tệp .c của dự án (cả main.c và tất cả các tệp trong không gian làm việc). Nó thực hiện như sau:

  1. xóa bình luận
  2. mở rộng các lệnh #include, thay thế chúng bằng nội dung của tệp được chỉ định. Quá trình này diễn ra đệ quy, bắt đầu từ tệp .c và nhập từng lệnh #include .h gặp phải và nếu chỉ thị #include cũng gặp trong tệp .h, bộ tiền xử lý cũng sẽ nhập chúng. Điều này dẫn đến một cây bao gồm. Xin lưu ý: nó không xử lý tình huống bao gồm kép, tức là cùng một tệp .h có thể được đưa vào nhiều lần nếu nó được #included ở nhiều vị trí trong dự án. Tình huống này cần được xử lý bằng defins.
  3. thực hiện thay thế macro - mở rộng macro
  4. thu thập các chỉ thị của trình biên dịch.

Bộ tiền xử lý tạo ra các tệp .i, khá thuận tiện khi tìm kiếm lỗi xây dựng - nếu chỉ vì tất cả các macro đều được tiết lộ đầy đủ trong đó. Việc lưu các tệp này có thể được bật trong cài đặt dự án.

Tại thời điểm này, trình xây dựng đã sẵn sàng biên dịch tất cả các tệp .c trong dự án - dưới dạng tệp .i. Chưa có kết nối nào giữa các tập tin.

2. Trình biên dịch

Sau khi đi qua bộ tiền xử lý, trình biên dịch sẽ tối ưu hóa và biên dịch từng tệp .i, tạo mã nhị phân. Đây là nơi bạn cần chỉ định loại bộ xử lý, bộ nhớ khả dụng, ngôn ngữ lập trình, mức độ tối ưu hóa và những thứ tương tự.

Trình biên dịch sẽ làm gì khi gặp một lệnh gọi hàm trong một số tệp .c không được mô tả trong tệp này? Anh ấy tìm kiếm nó trong các tiêu đề. Nếu tiêu đề nói rằng hàm nằm trong một tệp .c khác, thì nó chỉ để lại một con trỏ tới tệp khác này ở vị trí này.

Tại thời điểm này, trình xây dựng đã biên dịch tất cả các tệp .c của dự án thành các tệp .o. Chúng được gọi là các mô-đun được biên dịch. Bây giờ có các kết nối giữa các tệp dưới dạng con trỏ tại những nơi mà các hàm “nước ngoài” được gọi - nhưng đây vẫn là một số tệp khác nhau.

3. Trình liên kết

Hầu hết mọi thứ đã sẵn sàng, bạn chỉ cần kiểm tra tất cả các kết nối giữa các tệp - đi qua main.o và thay thế các con trỏ tới các chức năng của người khác - các mô-đun được biên dịch. Nếu một số chức năng từ các thư viện không được sử dụng, nó sẽ không được biên dịch ở giai đoạn trước hoặc sẽ không được trình liên kết thay thế ở bất kỳ đâu (tùy thuộc vào phương thức hoạt động của trình biên dịch mã). Trong mọi trường hợp, nó sẽ không được đưa vào mã nhị phân đã hoàn thành.

Trình liên kết cũng có thể thực hiện một số hành động cuối cùng trên tệp nhị phân, chẳng hạn như tính tổng kiểm tra của nó.

Dự án đầu tiên đang làm việc với các cổng I/O

3. Tạo dự án mới trong IAR

Sau khi khởi chạy IAR, một cửa sổ trung tâm thông tin xuất hiện mà chúng tôi không cần. Nhấp vào menu Dự án -> Tạo dự án mới. Chọn chuỗi công cụ: ARM (có thể bạn sẽ không có bất kỳ thứ gì khác trong danh sách đó), Mẫu dự án: C -> main.

Bây giờ bạn có một dự án C trống mới và tệp main.c.

4. Kết nối thư viện với dự án

Trong cửa sổ bên trái (“Không gian làm việc”), nhấp chuột phải vào menu và tạo một nhóm mới (Thêm -> Thêm nhóm), hãy gọi nó là CMSIS. Hãy tạo các nhóm StdPeriphLib, Startup và User theo cách tương tự. Bây giờ chúng ta thêm file vào nhóm (mình sẽ gạch chân tất cả các file để dễ theo dõi hơn).

Nhấp chuột phải vào CMSIS, Thêm, Thêm tệp - đi tới Thư viện/CMSIS/CM3, từ thư mục DeviceSupport/ST/STM32F10x (hỗ trợ chip) lấy system_stm32f10x.c (đây là mô tả về ngoại vi của cài đặt tinh thể và đồng hồ cụ thể). Trong thư mục CoreSupport (hỗ trợ kernel) cũng có core_cm3.c (đây là mô tả về lõi Cortex M3), nhưng chúng tôi sẽ không lấy nó - vì nó đã có trong trình biên dịch. Tôi sẽ viết thêm về điều này.

Tới nhóm Khởi động thêm tệp startup_stm32f10x_md_vl.s từ thư mục Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/iar. Đây là những hành động cần được thực hiện khi khởi động. Hầu như hoàn toàn đây là về việc thiết lập các trình xử lý ngắt khác nhau (bản thân các trình xử lý sẽ ở xa hơn một chút). Ngoài ra còn có các tệp dành cho các tinh thể khác, nhưng chúng tôi quan tâm đến md_vl - điều này có nghĩa là mật độ trung bình (dung lượng bộ nhớ trung bình, cũng có những tinh thể có dung lượng nhỏ và lớn), dòng giá trị (dòng đánh giá - tinh thể STM32F100 chỉ nhằm mục đích đánh giá khả năng và chuyển sang các họ sau).

Chúng ta đã hoàn tất CMSIS.

Tới nhóm StdPeriphLib thêm các tệp stm32f10x_rcc.c và stm32f10x_gpio.c từ thư mục Libraries/STM32F10x_StdPeriph_Driver/src. Đầu tiên là chức năng làm việc với hệ thống đồng hồ và thứ hai là làm việc với các chân I/O.

Tới nhóm Người dùng kéo main.c của chúng tôi. Điều này không cần thiết nhưng nó đẹp hơn.

Cây dự án GPIO bây giờ trông như thế này:

Không gian làm việc đã sẵn sàng, chúng tôi sẽ không thêm bất cứ thứ gì vào đó nữa.

Tất cả những gì còn lại là đặt một tệp khác vào thư mục dự án để kết nối các tiêu đề với tất cả các tệp thư viện ngoại vi. Bạn có thể tự viết nó, nhưng việc lấy một cái làm sẵn sẽ dễ dàng hơn. Chúng tôi truy cập stsw-stm32078.zip/an3268/stm32vldiscovery_package/Project/Examples/GPIOToggle - ở đó chúng tôi lấy tệp stm32f10x_conf.h (cấu hình dự án) và đặt nó vào thư mục “1. GPIO". Đây là tập tin làm sẵn duy nhất mà chúng tôi lấy.

stm32f10x_conf.h chỉ là một tập hợp bao gồm các mô-đun cần thiết và các hàm xác nhận. Hàm này sẽ được gọi khi có lỗi khi làm việc với các hàm thư viện ngoại vi: ví dụ như bỏ một ít rác vào hàm GPIO_WriteBit thay vì GPIOC - tóm lại là ST đã chơi an toàn. Trong hàm này, bạn chỉ cần bắt đầu một vòng lặp vô hạn - while(1); Chúng ta vẫn cần vào stm32f10x_conf.h - để nhận xét các dòng bao gồm các tệp của các thiết bị ngoại vi không cần thiết, chỉ để lại stm32f10x_rcc.h, stm32f10x_gpio.h và misc.h - để chúng ta có thể tự viết nó.

5. Thiết lập dự án

Nhấp chuột phải vào tên dự án trong cửa sổ Workspace:

  1. Tùy chọn chung -> Mục tiêu -> Biến thể bộ xử lý: chọn “Thiết bị”, nhấn nút bên phải
Chọn ST -> STM32F100 -> ST STM32F100xB. Đây là bộ điều khiển của chúng tôi. 2. Tùy chọn chung -> Cấu hình thư viện -> CMSIS: chọn hộp Sử dụng CMSIS. Vì vậy chúng ta sẽ sử dụng thư viện CMSIS được tích hợp sẵn trong trình biên dịch. Kể từ phiên bản 6.30, IAR bắt đầu được cung cấp CMSIS tích hợp và điều này có vẻ tốt hơn - nhưng nó gây ra một số nhầm lẫn với các dự án cũ hơn. 3. Trình biên dịch C/C++ -> Bộ tiền xử lý. Ở đây chúng tôi viết đường dẫn đến các thư mục thư viện:
$PROJ_DIR$\
$PROJ_DIR$\..\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
$PROJ_DIR$\..\Libraries\STM32F10x_StdPeriph_Driver\inc
Macro $PROJ_DIR$ có nghĩa là thư mục hiện tại(thư mục dự án) và.. - di chuyển lên một cấp cao hơn. Chúng tôi đã chỉ định các đường dẫn đến thư mục có mô tả về tinh thể, cũng như các tệp tiêu đề của thư viện ngoại vi, vì tất cả các tệp .c trong dự án đều bao gồm các tiêu đề của chúng và trình biên dịch phải biết tìm chúng ở đâu. Tại đây, bạn cũng cần viết USE\_STDPERIPH\_DRIVER trong các ký hiệu được xác định. Điều này sẽ kết nối tập tin cần thiết cấu hình (ví dụ: stm32f10x_conf.h đã đề cập) cho dự án. Vì vậy, tab Preprocessor sẽ trông như thế này: * Debugger –> Setup –> Driver: chọn ST–Link, vì đây là bộ lập trình được tích hợp sẵn trong bảng Discovery. Bây giờ chúng ta tự cấu hình bộ lập trình: * Trình gỡ lỗi -> ST–LINK -> Giao diện: chọn SWD (bộ lập trình trên bo mạch được kết nối với bộ điều khiển qua SWD, không qua JTAG). * Trình gỡ lỗi -> Tải xuống: chọn hộp Sử dụng (các) bộ tải flash, “Tải chương trình cơ sở vào bộ nhớ flash.” Điều đó hợp lý, nếu không có nó sẽ không có gì ngập lụt.## 6. Viết code Đầu tiên mình sẽ viết code này sẽ làm gì. Anh ấy sẽ chứng minh điêu đơn giản, đèn LED nhấp nháy (PC8 trên bảng Discovery) kèm theo khoảng dừng trong vòng lặp vô tận. Chúng tôi bao gồm tệp tiêu đề cấu hình dự án, stm32f10x\_conf.h. Trong đó, chúng tôi tìm thấy dòng #include “stm32f10x\_exti.h” - đây là dòng 35 và nhận xét nó bằng hai dấu gạch chéo. Thực tế là dự án của chúng tôi sẽ không cần mô-đun EXTI. Tệp main.c đã có hàm int main và hành động duy nhất trong đó là trả về 0. Chúng tôi xóa dòng này (chúng tôi sẽ không trả về bất kỳ giá trị nào), thay đổi loại hàm thành void (vì lý do tương tự), và viết một vòng lặp vô hạn:
#include "stm32f10x_conf.h" 

khoảng trống chính()
{
trong khi(1)
{
}
}

### Khởi chạy mô-đun GPIO Các cổng đầu vào/đầu ra trong STM32 được gọi là GPIO - Đầu vào/Đầu ra mục đích chung. Đó là lý do tại sao chúng tôi đưa vào thư viện stm32f10x_gpio.c. Tuy nhiên, đây không phải là tất cả những gì chúng ta cần, một lý thuyết nhỏ: Tất cả các thiết bị ngoại vi trên chip đều bị tắt theo mặc định, cả từ nguồn điện lẫn tần số xung nhịp. Để bật nó lên, bạn cần gửi tín hiệu đồng hồ. Điều này được quản lý bởi mô-đun RCC và có một tệp stm32f10x_rcc.c để làm việc với nó. Mô-đun GPIO bị treo trên bus APB2. Ngoài ra còn có AHB (một dạng tương tự của bus bộ xử lý-bắc cầu) và APB1 (cũng như APB2 - một dạng tương tự của bus cầu bắc-cầu nam). Vì vậy, điều đầu tiên chúng ta cần làm là kích hoạt xung nhịp mô-đun GPIOC. Đây là mô-đun chịu trách nhiệm về PORTC; còn có GPIOA, GPIOB, v.v. Việc này được thực hiện như sau: RCC\_APB2PeriphClockCmd(RCC\_APB2Periph_GPIOC, ENABLE); Thật đơn giản - chúng tôi gọi chức năng gửi tín hiệu đồng hồ từ bus APB2 đến mô-đun GPIOC và từ đó bật mô-đun này. Tất nhiên, chúng tôi làm điều này ngay từ đầu. hàm rỗng chủ yếu. Đây chỉ là những điều cơ bản bạn cần hiểu. Tôi cũng có một [bài viết chi tiết về mô-đun GPIO](/index.php/stm32-%e2%86%92-%d0%bf%d0%be%d1%80%d1%82%d1%8b- gpio / "STM32 → cổng GPIO"). ### Cấu hình mô-đun GPIOC Còn lại rất ít, bạn cần cấu hình mô-đun GPIOC. Chúng tôi cài đặt chân đầu ra (cũng có chức năng đầu vào và thay thế), điều chỉnh độ sắc nét của mặt trước (vì mục đích tương thích EM) và trình điều khiển đầu ra (kéo đẩy hoặc nguồn mở). Chúng tôi thực hiện việc này ngay sau khi khởi tạo cổng. GPIO\_InitTypeDef GPIO\_InitStructure; GPIO\_InitStructure.GPIO\_Speed ​​​= GPIO\_Speed\_2MHz; GPIO\_InitStructure.GPIO\_Mode = GPIO\_Mode\_Out_PP; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_8; GPIO\_Init(GPIOC, &GPIO\_InitStructure); Thôi vậy đó, sau này chân PC8 sẽ hoạt động như một đầu ra kéo đẩy với các cạnh tương đối nhẵn ( tần số tối đa chuyển mạch 2 MHz. Các cạnh sắc nét là 50 MHz). Chúng ta sẽ không nhận thấy độ mịn của mặt trước bằng mắt nhưng có thể nhìn thấy nó trên máy hiện sóng. ### Bật đèn LED Gọi hàm GPIO\_WriteBit(GPIOC, GPIO\_Pin\_8, Bit\_SET); Đèn LED sẽ bật. ### Bật và tắt nó trong một vòng lặp Trong vòng lặp while(1), chúng ta viết mã để bật, tạm dừng, tắt và tạm dừng lại:

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);  vì(i=0; tôi<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
vì(i=0; tôi<1000000; i++);

Do đó, toàn bộ tệp main.c trông như thế này:

#include "stm32f10x_conf.h"

khoảng trống chính()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed ​​​​= GPIO_Speed_2 MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOC, &GPIO_InitCấu trúc);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);

int tôi;
trong khi(1)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
vì(i=0; tôi<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET); vì(i=0; tôi<1000000; i++); } }

## 7. Xuất phát nào! Chúng ta kết nối bo mạch STM32VLDiscovery với máy tính qua microUSB, nhấp vào nút Tải xuống và Gỡ lỗi trong IAR. Chương trình được tải lên bộ vi điều khiển (bạn sẽ thấy một cửa sổ có thanh tiến trình đóng nhanh - kích thước của chương trình quá nhỏ) và quá trình gỡ lỗi bắt đầu. IAR dừng ở lệnh đầu tiên của mã (điều này khá thuận tiện khi gỡ lỗi), bạn cần khởi động nó bằng nút Bắt đầu. Mọi thứ sẽ hoạt động - đèn LED PC8 màu xanh lam trên bo mạch STM32VLDiscovery Như thường lệ, bạn có thể tải xuống kho lưu trữ với dự án GPIO. May mắn thay, bạn có thể lưu dự án này và sử dụng nó làm mẫu để không phải thực hiện lại toàn bộ quá trình thiết lập. Toàn bộ chu trình: 1. Cổng I/O (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - từ 0 đến RTOS. 2: Hẹn giờ và ngắt") (/index.php/stm32-from_zero_to_rtos-3_timer_outputs/ " STM32 - từ 0 đến RTOS 3: Đầu ra hẹn giờ")

](/index.php/stm32-from_zero_to_rtos-4_exti_nvic/ “STM32 - từ 0 đến RTOS. 4: Ngắt bên ngoài và NVIC”) 5. Cài đặt FreeRTOS