STM32F4
STMicroelectronics
ɥuɐɥʇ ıoʇ@thanhduongvs
1
STM32F4
#01 : GPIO STM32F4 @01. Giớ i thiệu GPIO: GPIO (General-Purpose Input/Output) hiểu nôm na là đầu vào - đầu ra sử dụng chung:
Đối vớ i các dòng STM32 thì mỗi port có 16 chân IO. Ngoài chức năng IO muố n sử dụng ngoại vi phải thiết lậ p chân đó là Alternate hoặc Analog. ở n Mỗi chân IO bên trong chip đều gắn thêm điện tr ở nội pull up và pull down.
Chi tiết bạn có thể tham khảo Reference manual trang 185, và sơ đồ chân, kiểu chân tham khảo datasheet.
@02. K ết nối phần cứ ng: ng:
Bài viết sẽ đượ c thực hành trên board MiniTestF4. Sơ đồ nguyên lý và mạch in vẽ bằng Altium đã đượ c
đính kèm bên trên. Trong bài lập trình ICViet đã khai báo chân PA0 PA0 là input, chân chân PB0PB1 là output. Bạn có thể k ết nối như sơ đồ bên đồ bên dướ i.i. Riêng phần mạch nạ p bạn có thể sử dụng mạch nạ p bất k ỳ cho ARM chuẩn SWD hoặc ST-LinkV2 của ICViet.
ɥuɐɥʇ ıoʇ@thanhduongvs
2
STM32F4
#01 : GPIO STM32F4 @01. Giớ i thiệu GPIO: GPIO (General-Purpose Input/Output) hiểu nôm na là đầu vào - đầu ra sử dụng chung:
Đối vớ i các dòng STM32 thì mỗi port có 16 chân IO. Ngoài chức năng IO muố n sử dụng ngoại vi phải thiết lậ p chân đó là Alternate hoặc Analog. ở n Mỗi chân IO bên trong chip đều gắn thêm điện tr ở nội pull up và pull down.
Chi tiết bạn có thể tham khảo Reference manual trang 185, và sơ đồ chân, kiểu chân tham khảo datasheet.
@02. K ết nối phần cứ ng: ng:
Bài viết sẽ đượ c thực hành trên board MiniTestF4. Sơ đồ nguyên lý và mạch in vẽ bằng Altium đã đượ c
đính kèm bên trên. Trong bài lập trình ICViet đã khai báo chân PA0 PA0 là input, chân chân PB0PB1 là output. Bạn có thể k ết nối như sơ đồ bên đồ bên dướ i.i. Riêng phần mạch nạ p bạn có thể sử dụng mạch nạ p bất k ỳ cho ARM chuẩn SWD hoặc ST-LinkV2 của ICViet.
ɥuɐɥʇ ıoʇ@thanhduongvs
2
STM32F4
@03. Lập trình GPIO cơ bản: Trướ c hết ICViet xin thống nhất vớ i các bạn phương pháp học và tiế p cận ARM không giống như các loại vi điều khiển trước đó bạn đã từng học, các b ạn không nên can thiệ p sâu vào bên trong các thanh thanh ghi, vì r ất nhiều thanh ghi để cấu thành một ngoại vi. R ất may mỗi hãng đều support sample code và thư viện ngoại vi chuẩn để bạn tiện lậ p trình mà không cần kiến thức chuyên sâu hay hi ểu rõ datasheet của chip. Điển hình ST sử dụng thư viện chuẩn CMSIS. Tôi sẽ cố gắng giải thích đầy đủ các dòng lệnh trong các bài viết nhưng những đoạn code đã giả i thích r ồi thì tôi sẽ không giải thích lại trong bài sau, những đoạn code thiếu tài liệu hay giải thích chưa rõ ràng củ a ST tôi cũng sẽ rút gọn hoặc bỏ qua bớ t không giải thích, hoặc do trình độ chuyên môn tôi chưa cao đối vớ i những lệnh này tôi cũng sẽ lượ c bỏ giải thích mong bạn đọc thông cảm. Nếu bạn muốn tìm hiểu sâu hơn những code đã lượ c bỏ phần giải thích này bạn có thể tr ỏ đến nguồn gốc của nó, tìm thanh ghi và tra datasheet để đượ c giải thích tốt nhất.
ỡ ngàng trướ c một số dòng Một nền tảng r ất cần thiết đó là bạn phải nắm vững cấu trúc C để không phải ngỡ ngàng code nâng cao. Đầu tiên bạn tải sample code được đính kèm bên trên sau đó vào đườ ng ng dẫn GPIO\EWARM mở file file Project.eww (IAR IDE Workspace) cho IAR ho ặc vào đườ ng ng dẫn GPIO\MDK-ARM mở file file Project.uvproj (µVision4 Project) cho Keil.
Dĩ nhiên khi xem một chương trình tôi khuyên bạn nên xem ở hàm ở hàm main trướ c vì trong một chương trình C thì main luôn thực thi trướ c. c. Bắt đầu chương trình main thì dòng thứ 9 sẽ đượ c thực hiện trướ c nó sẽ nhảy đến hàm GPIO_Configuration(). GPIO_Configuration(). Hàm GPIO_Configuration() nằm từ dòng 23 -> 39.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
void GPIO_Configuration(voi GPIO_Configuration(void) d) { RCC_AHB1Peri phClockCmd(RC C_AHB1Periph_ GPIOB | RCC_AHB1Peri ph_GPIOA,ENA BLE); /* Configure PB0 PB1 in output pushpull mode */ GPIO_InitStru cture.GPIO_Pi n = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStru cture.GPIO_Mo de = GPIO_Mode_OU T; GPIO_InitStr ucture.GPIO_OT ype = GPIO_OType_PP ; GPIO_InitStru cture.GPIO_Sp eed = GPIO_Speed_100 MHz; GPIO_InitStru cture.GPIO_Pu Pd = GPIO_PuPd_NO PULL; GPIO_Init(GPI OB, &GPIO_InitStr ucture); /* Configure PA0 in input mode */ GPIO_InitStr ucture.GPIO_Pi n = GPIO_Pin_0; GPIO_InitStru cture.GPIO_Mo de = GPIO_Mode_IN ; GPIO_InitStru cture.GPIO_Pu Pd = GPIO_PuPd_UP ; GPIO_Init(GPI OA, &GPIO_InitStr ucture); }
Và dĩ nhiên là trong bất k ỳ vi điều khiển nào thì lệnh sẽ chạy liên tục từ trên xuống dướ i nên tôi sẽ giải thích các đoạn code trên theo thứ tự từ trên xuống dướ i,i, một lưu ý là bạn sẽ thắc mắc tên biến hay hàm này là gì, được định nghĩa như thế nào?, nằm ở đâu? ở đâu? ở m mỗi trình biên dịch điều giúp bạn điều này, bạn có thể click chuột phải vào biến(hàm) cần tìm chọn tr ỏ đến thông tin , tùy vào trình biên dịch sẽ có cách chọn khác nhau. Tôi có hướ ng ng dẫn khái quát ở các các bài gi ớ i thiệu cơ bản về trình biên dịch IAR, Keil. Ở dòng 25: dòng lệnh này có nghĩa là bật clock của khối GPIOA, GPIOB. Trong mỗi chip STM32 thì điề u này r ất cần thiết, mỗi ngoại vi đều có một công tắc bật như trên, nếu bạn quên bướ c này thì chắc chắn bạn sẽ không thể làm gì đượ c nữa. Sau này khi bạn lậ p trình ngoại vi khác chẳng hạn UART, SPI, I2C thì bạn đều phải bậc clock khối đó lên chip mớ i có thể cấ p xung nhị p p hoạt động cho khối đó. Dòng 28: do phần cứng tôi chọn sử dụng chân PB0, PB1 nên dòng này tôi phải khai báo, nếu thêm nhiều chân nữa thì bạn chú ý dấu "|", hoặc nếu muốn sử dụng tất cả các chân cùng lúc bạn có thể dùng GPIO_Pin_All sẽ
ɥuɐɥʇ ıoʇ@thanhduongvs
3
STM32F4
ngắn gọn hơn rất nhiều. Chú ý là bạn sử dụng chân nào thì khai báo chân đó, không nên khai báo tràn lang, nhiều khi bạn sẽ lập trình chân đó ở m một công việc khác, hoặc bạn có thể khai báo nhầm lên chân nạ p SWD thì bạn sẽ không nạ p đượ c chip nữa mà phải tìm cách khác để cứu chip. Dòng 29: chọn mode out. Dòng 30: chọn chế độ pushpull, chế độ này hay đượ c sử dụng để điều khiển GPIO nhất. còn một mode khác đó là Open drain mode này sẽ mắc một cặ p Fet thuận nghịch trước đầu ra của chip, bạn có thể xem Reference manual Figure 17 trang 186 để hiểu rõ. Dòng 31: chọn tốc độ đầu ra của chân out, bạn nên chọn tốc độ cao nhất. Dòng 32: chế độ đầu ra nopull có nghĩa là không có điện tr ở ở nnội kéo lên mức cao và mức thấ p. Trên thực tế ở mode output thì điện tr ở ở n nội không có ý nghĩa gì cả. Dòng 33: bạn chú ý "GPIO_InitStructure" là một kiểu cấu trúc trong C, kiểu này đượ c khai báo ở dòng dòng thứ 3. Hàm GPIO_Init(GPIOB, &GPIO_InitStructure); &GPIO_InitStructure); truy t ruyền 2 đối số xuống để thiết lậ p thanh ghi cho phần cứng. Từ dòng 35 -> 38 tương tự như các bước trên. Riêng dòng 37 khai báo điện tr ở ở treo treo mức cao. Điều này có nghĩa là sao ???? Bạn xem chân PA0 có n ối vớ i nút nhấn, vậy ở tr tr ạng thái không nhấn (thườ ng ng hở ) thì sẽ không xác định đượ c tr ạng thái của nó, nên phải thêm điện tr ở ở treo treo mức cao để chân đượ c treo lên mức cao, khi đượ c treo lên mức cao r ồi bạn nhấn nút xuống mớ i có tác d ụng đượ c, c, ngay lậ p tức kéo chân PA0 xu ống mức thấp do điện tr ở ở ccủa ở treo dây dẫn phải nhỏ hơn điện tr ở treo mức cao. Sau khi k ết ết thúc hàm GPIO_Configuration() chương trình quay trở l lại hàm main. Ở dòng 12 bạn thấy hàm GPIO_ReadInputDataBit() GPIO_ReadInputDataBit() có chức năng trả về 0 hoặc 1, tùy vào tr ạng ạng thái chân PA0 đọc về. Câu lệnh if sẽ nhận giá tr ị đúng-sai này để nhảy vào dòng 14 hoặc 18, ở tr tr ạng thái không nhấn có nghĩa là chân PA0 mức cao thì chương trình nhảy vào dòng 14 set hai chân PB0-PB1 lên mức cao, theo như trên sơ đồ test bên trên thì chỉ có LED-D2 sáng, ngượ c lại ở tr tr ạng thái nhấn nút thì LED-D1 sáng.
Ngoài ra trong thư viện stm32f4xx_gpio còn r ất nhiều hàm để bạn tham khảo, tôi trích ra từ stm32f4xx_gpio.h
/* GPIO Read and Write functions **********************************************/ uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); Bên trên các hàm nằm trong file stm32f4xx_gpio.c đều có chú thích đầy đủ công dụng và chức năng, bạn có thể thử từng hàm để biết rõ cách hoạt động của chúng.
@04. Code trong bài vi ết: main.c: 01
#include "stm32f4xx.h "
ɥuɐɥʇ ıoʇ@thanhduongvs
4
STM32F4
02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
GPIO_InitTypeDef GPIO_InitStructure; void GPIO_Configu ration(void); void Delay(__IO uint32_t nCount); int main(void) { GPIO_Configuration(); while (1) { if(GPIO_ReadI nputDataBit(G PIOA , GPIO_Pin_0)) { GPIO_SetBits( GPIOB ,GPIO_Pin_0 | GPIO_Pin_1 ); } else { GPIO_ResetBit s(GPIOB ,GPIO_Pin_0 | GPIO_Pin_1 ); } } } void GPIO_Configuration(voi GPIO_Configuration(void) d) { RCC_AHB1Perip hClockCmd(RCC _AHB1Periph_G PIOB | RCC_AHB1Peri ph_GPIOA, ENABLE); /* Configure PB0 PB1 in output pushpull mode */ GPIO_InitStru cture.GPIO_Pi n = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStru cture.GPIO_Mo de = GPIO_Mode_OU T; GPIO_InitStru cture.GPIO_OT ype = GPIO_OType_PP; GPIO_InitStru cture.GPIO_Sp eed = GPIO_Speed_100 MHz; GPIO_InitStru cture.GPIO_Pu Pd = GPIO_PuPd_NO PULL; GPIO_Init(GPI OB, &GPIO_InitStr ucture); /* Configure PA0 in input mode */ GPIO_InitStru cture.GPIO_Pi n = GPIO_Pin_0; GPIO_InitStr ucture.GPIO_Mo de = GPIO_Mode_IN ; GPIO_InitStru cture.GPIO_Pu Pd = GPIO_PuPd_UP ; GPIO_Init(GPI OA, &GPIO_InitStr ucture); } void Delay(__IO uint32_t nCount) { while(nCount--) { } } #ifdef USE_FULL_ASSERT void assert_faile d(uint8_t* file, uint32_t line) { /* User can add his own implementatio n to report the file name and line number, ex: printf("Wron g parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
ɥuɐɥʇ ıoʇ@thanhduongvs
5
STM32F4
#02 : INTERRUPT STM32F4 @01. Giớ i thiệu khái quát ng ắt và ngắt ngoài: Thuật ngữ ngắt (Interrupt) đượ c dùng r ất nhiều trong vi điều khiển, r ất quan tr ọng trong lập trình tương tác vớ i ngoại vi, vậy ngắt là gì ??? ứng dụng để làm gì ??? Như các bạn đã biế t trong lập trình vi điều khiển luôn phải tuân thủ quy tắc tuần tự từ trên xuống dướ i, và chỉ thực hiện trong chương trình chính. Vậ y bạn có bao giờ thắc mắc nếu như chương trình chính của bạn ngốn quá nhiều thờ i gian thực hiện, vậy làm sao bạn thực hiện một việc khác ngay tức thờ i đượ c. Tôi ví dụ một chương trình cơ bản sau: void main(void){ while(1){ LED1 = 1; delay_ms(1000); LED1 = 0; delay_ms(1000); if(button) LED2 = 1; else LED2 = 0; } }
Trong chương trình này LED1 sẽ sáng 1s và tắt 1s cứ như vậy mà lặ p lại chu trình. Tại một thời điểm nào đó khi bạn nhấn nút nhấn xuống và chắc chắn là LED2 sẽ không sáng liền ngay lúc bạn nhấn, mà phải đợi cho đến khi bóng LED1 tắt sau 1s dòng chương trình mớ i nhảy đến lệnh if(). Điều này sẽ làm bạn khó chịu, nhưng chắc chắn là các lập trình viên đi trướ c sẽ có cách gi ải quyết, và tôi chọn sử dụng ngắt ngoài cho bài viết hôm nay. Ngắt có nghĩa là một sự gián đoạn chương trình chính, ngưng thự c thi ở chương trình chính mà nhảy đế n một địa chỉ nào đó để giải quyết vấn đề ở đó, sau khi xử lý xong sẽ quay về chương trình chính ngay tại nơi mà ban nãy nó đã thoát khỏi chương trình chính. Ngắt ngoài thì ta sử dụng một tín hiệu bên ngoài để tạo ra ngắt, chẳng hạn input trên GPIO.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối phần cứng như bên dưới để tiện cho bài viết hôm nay.
ɥuɐɥʇ ıoʇ@thanhduongvs
6
STM32F4
@03. Lập trình ng ắt ngoài: Đối vớ i sử dụng ngắt ở dòng STM32 này bạn cần lưu ý là tuy ngắt ngoài có thể sử dụng trên bất k ỳ chân IO nhưng bạn chỉ sử dụng đượ c tối đa 16 chân ngắt ngoài. Giả sử nếu chân PA0 đã sử dụng r ồi thì trên line0 đó bạn không đượ c sử dụng chân PB0 hay PC0... gì nữa, đó là lý do chỉ giớ i hạn ở 16 ngắt ngoài. Dòng 51 : là k ết nối chân IO vớ i khối ngắt, một ngoại vi nếu có liên quan đến môi trườ ng thì một điều chắc chắn là bạn phải tạo một sụ liên k ết ngoại vi đó vớ i chân IO bằng câu lệnh này. Dòng 54 : bên trên đã có giớ i thiệu, ngắt trong STM32 có 16 line, khai báo line0 trùng vớ i chân IO PB0. Dòng 55 : chọn mode interrupt. Dòng 56 : chọn ngắt Falling (ngắt xảy ra khi tín hiệu chuyển từ mức cao xuống mức thấ p), bạn nên lưu ý ở chế độ này một tín hiệu ngắt xảy ra trong thời điểm gọi là tức thờ i, ngay thời điểm tín hiệu chuyển từ mức cao xuống mức thấ p (Falling) hay từ mức thấ p lên mức cao (Rising). VD ngắt ngoài đượ c lấy từ nút nhấn thì ngắt xảy ra ngay khi bạn bấm nút nhấn tỳ xuống và thời điểm bạn nhả tay ra khỏi nút nhấn. Trong STM32 có 3 mode ngắt cho bạn chọn lựa là Rising, Falling hay cả Rising-Falling.
Sau khi khai báo xong các dòng trên xem như bạn đã hoàn tất các thủ tục liên quan đến ngắt nhưng vấn đề đặt ra là khi ngắt chương trình sẽ nhảy đến đâu, như trong hợ p ngữ bạn dễ dàng định hướng đượ c ngắt sẽ nhảy đến địa chỉ ngắt và thực hiện chương trình ở địa chỉ đó vậy trong C địa chỉ đó sẽ nằm ở đâu??? Sẽ không có địa chỉ nhưng đượ c thay thế bằng hàm và các hàm liên quan đến ngắt này sẽ nằm ở file stm32f4xx_it.c, file này nằm cùng thư mục vớ i main.c các hàm ngắt này đều có tên chuẩn và đượ c quản lý bở i phần cứng NVIC interrupts (IRQ). Dòng 61 : khai báo ngắt EXTI0_IRQn vớ i NVIC. Vớ i khai báo này thì hàm ngắt chắc chắn phải có tên là EXTI0_IRQHandler, hàm này không có đối số truyền và nhận nên tên của nó trong stm32f4xx_it.c là ―void EXTI0_IRQHandler(void)‖ hàm này do bạn tạo chứ trình biên dịch không tự sinh ra. Bạn nên chú ý chữ ―n‖ ở khai báo và ―Handler‖ ở hàm ngắt còn ―EXTI0_IRQ‖ tên chung.
Hai dòng 62 & 63 là khai báo độ ưu tiên ngắt số càng nhỏ độ ưu tiên càng lớn. Trong trườ ng hợ p bạn sử dụng r ất nhiều ngắt và chương trình bạn cần hàm ngắt nào nên làm vi ệc trước để đảm bảo tính thờ i gian thực thì hai dòng này r ất cần thiết. bạn có thể tham khảo chi tiết tại hàm misc.c chú thích gi ải thích r ất rõ ràng. Chưa khai báo group thì mặc định là group0 (NVIC_PriorityGroup_0). Hàm EXTILine9_Config() cũng tương tự như trên nên tôi sẽ không giải thích thêm, có một điểm nhỏ bạn cần lưu ý ở dòng 93 tôi khai báo EXTI9_5_IRQn dòng lệnh này có nghĩa là ngắt từ line5 -> line9 phải nhảy chung đến một hàm ngắt, tương tự cho EXTI15_10_IRQn line 10 -> 15. Trong hàm main() tôi cho bóng LED D1 liên tục chớ p tắt, khi xảy ra một sự kiện ngắt VD như bạn nhấn nút S1 thì LED D2 s ẽ thay đổi tr ạng thái hiện tại của nó. Nút nhấn S1 tôi chọn mode falling nên thời điểm bạn nhấn xuống bóng LED D1 sẽ ngay lậ p tức thay đổi tr ạng thái, còn nút S2 S3 tôi chọn mode rising nên khi bạn nhấn xuống và nhả tay lên LED D3 D4 mới thay đổi tr ạng thái. Trong quá trình nhấn bạn sẽ cảm thấy nhấn không như lý thuyết cho lắm vì lúc ăn lúc không, điều đó do nút nhấn là tiếp điểm cơ khí khi bạn nhấn hay nhả sẽ gây nhiễu tiếp điểm, để tránh tình tr ạng này bạn có thể dùng một giải thuật gì đó để giải quyết vấn đề nhiễu này.
@04. Code trong bài vi ết: main.c 001 002 003 004 005 006
#include "stm32f4xx.h" GPIO_InitTypeDef NVIC_InitTypeDef EXTI_InitTypeDef
GPIO_InitStructure; NVIC_InitStructure; EXTI_InitStructure;
ɥuɐɥʇ ıoʇ@thanhduongvs
7
STM32F4
007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067
void GPIO_Configuration(void); void EXTILine0_Config(void); void EXTILine9_Config(void); void Delay(__IO uint32_t nCount); int main(void) { GPIO_Configuration(); EXTILine0_Config(); EXTILine9_Config(); while (1) { GPIO_ToggleBits(GPIOB,GPIO_Pin_1); Delay(10000000); } }
void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &GPIO_InitStructure); } void EXTILine0_Config(void) { /* Enable GPIOB clock */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* Enable SYSCFG clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* Configure PB0 pin as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Connect EXTI Line0 to PB0 pin */ SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource0); /* Configure EXTI Line0 */ EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* Enable and set EXTI Line0 Interrupt to the lowest priority */ NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
ɥuɐɥʇ ıoʇ@thanhduongvs
8
STM32F4
068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
void EXTILine9_Config(void) { /* Enable GPIOB clock */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* Enable SYSCFG clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* Configure PB5 PB6 pin as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Connect EXTI Line5, Line6 to PB5,PB6 pin */ SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource5); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource6); /* Configure EXTI Line5, Line6 */ EXTI_InitStructure.EXTI_Line = EXTI_Line5 | EXTI_Line6; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* Enable and set EXTI Line5, Line6 Interrupt to the lowest priority */ NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
void Delay(__IO uint32_t nCount) { while(nCount--) { } }
#ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ while (1) {} } #endif
stm32f4xx_it.c 01 02 03 04 05
#include "stm32f4xx_it.h" void NMI_Handler(void) { }
ɥuɐɥʇ ıoʇ@thanhduongvs
9
STM32F4
06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) {} } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1) {} } void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) {} } void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) {} } void DebugMon_Handler(void) {} void SVC_Handler(void) {} void PendSV_Handler(void) {} void SysTick_Handler(void) {} void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { GPIO_ToggleBits(GPIOB,GPIO_Pin_2); EXTI->PR = EXTI_Line0; } } void EXTI9_5_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line5) != RESET) { GPIO_ToggleBits(GPIOB,GPIO_Pin_3); EXTI->PR = EXTI_Line5; } if(EXTI_GetITStatus(EXTI_Line6) != RESET) { GPIO_ToggleBits(GPIOB,GPIO_Pin_4); EXTI->PR = EXTI_Line6;
ɥuɐɥʇ ıoʇ@thanhduongvs
10
STM32F4
67 68
} }
#03 : SYSTEM TICK STM32F4 @01. Giớ i thiệu system tick: System tick là bộ định thời timer độc lậ p vớ i các timer ngoại vi, system tick có độ phân giải tối đa 24bit và không có lệnh đọc thanh ghi từ system tick. Tôi cho bạn một ví dụ cơ bản để dễ hình dung, nếu bạn muốn làm một đồng hồ đếm thờ i gian thì bạn sẽ dễ dàng k ết hợ p lệnh for và delay nhưng khi viết trong chương trình chính thời gian đếm của bạn sẽ không phải là hằng số do mỗi khi bạn thêm một dòng lệnh thì thời gian trong chương trình chính của bạn dãn ra. Trong trườ ng hợ p này system tick sẽ phát huy tác dụng, như đã đề cậ p bên trên system tick là một timer đếm thờ i gian, mỗi khi cài đặt cho system tick thì system tick sẽ định thờ i khoảng bao lâu nhảy vào ngắt , con số thờ i gian này là hằng số, việc nhảy vào ngắt cũng hoàn toàn tự động.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối theo sơ đồ bên dướ i
ɥuɐɥʇ ıoʇ@thanhduongvs
11
STM32F4
@03. Lập trình system tick: Việc cài đặt system tick hết sức đơn giản, chỉ gồm 1 bướ c. Dòng 10 : tôi muốn cứ 1ms thì nhảy vào ngắt một lần có nghĩa là tần số là 1 Khz con số 1000 mà tôi điền vào. Đối số truyền vào cho hàm SysTick_Config() là SystemCoreClock / 1000 = 168000 không lớn hơn số 24bit (SystemCoreClock đã được định nghĩa là 168000000), có nghĩa là có 168000 lần đếm mà mỗi lần đếm thờ i gian bằng 1 chu k ỳ máy, sẽ nhảy vào ngắt thực hiện một lần (1 chu k ỳ máy : 1/168000000 = 0.00595 us). Do lý thuyết trên nên tôi đã rút gọn lại thành một công thức đơn giản SysTick_Config(SystemCoreClock / F)
Trong đó F là tầ n số hàm ngắt của bạn tính bằng đơn vị Hz. Bạn quan sát từ dòng 51 -> 59 hàm ngắt SysTick_Handler() trong file stm32f4xx_it.c chứa chương trình mà tôi sẽ thực hiện mỗi 1ms/1 lần.
51 52 53 54 55 56 57 58 59
void SysTick_Handler(void) { static uint32_t time=0; if(++time>1000) { GPIO_ToggleBits(GPIOB ,GPIO_Pin_1); time = 0; } }
Các dòng code trên có nghĩa là nếu time > 1000 thì s ẽ vào hàm if th ực hiện thay đổi tr ạng thái chân PB1 1 lần, mỗi lần thay đổi tr ạng thái chân PB1 = 1s. Khai báo kiểu static làm cho biến time vẫn giữ nguyên giá tr ị mỗi khi hàm ngắt thực hiện xong và thoát ra ngoài. Ngoài ra trong chương trình chính ở f ile main.c tôi cho chân PB0 thay đổi tr ạng thái ở tần số thấp hơn, rất dễ dàng quan sát hai chân PB0 PB1 có hai t ần số khác nhau.
@04. Code trong bài vi ết: main.c 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
#include "stm32f4xx.h" GPIO_InitTypeDef GPIO_InitStructure; void GPIO_Configuration(void); void Delay(__IO uint32_t nCount); int main(void) { GPIO_Configuration(); SysTick_Config(SystemCoreClock / 1000); while (1) { GPIO_ToggleBits(GPIOB ,GPIO_Pin_0); Delay(50000000); } }
ɥuɐɥʇ ıoʇ@thanhduongvs
12
STM32F4
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
void GPIO_Configuration(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA, ENABLE); /* Configure PB0 PB1 in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Configure PA0 in input mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); } void Delay(__IO uint32_t nCount) { while(nCount--) { } } #ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
stm32f4xx_it.c 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
#include "stm32f4xx_it.h" void NMI_Handler(void) { } void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { } } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1)
ɥuɐɥʇ ıoʇ@thanhduongvs
13
STM32F4
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
{ } } void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) { } } void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) { } } void SVC_Handler(void) { } void DebugMon_Handler(void) { } void PendSV_Handler(void) { } void SysTick_Handler(void) { static uint32_t time=0; if(++time>1000) { GPIO_ToggleBits(GPIOB ,GPIO_Pin_1); time = 0; } }
ɥuɐɥʇ ıoʇ@thanhduongvs
14
STM32F4
#04 : TIMER STM32F4 @01. Giớ i thiệu cơ bản về timer trong STM32F4: Timer trong STM32F4 có r ất nhiều chức năng chẳng hạn như bộ đếm Counter, PWM, Input Capture ngoài ra còn một số chức năng đặt biệt để điều khiển động cơ như Encoder, Hall Sensors. Trong STM32F4 Timer 1 và Timer 8 có cùng t ần số vớ i xung clock hệ thống, các Timer còn l ại chỉ bằng một nửa. Riêng Timer 2 và Timer 5 là Timer 32bit, các Timer còn l ại 16bit. Timer 6 và Timer 7 là 2 Timer vớ i các ch ức năng cơ bản không giao tiếp đượ c với môi trườ ng.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối như sơ đồ bên dướ i.
@03. Lập trình timer cơ bản: Trong bài viết này tôi sẽ thiết lậ p ngắt định thờ i cho Timer 4, mỗi 1ms sẽ nhảy vào ngắt một lần, tương tự như bài viết system tick. Và Timer 2 tôi sẽ dùng nó để đo thờ i gian các lệnh xử lý ở chương trình chính sau đó quan sát bằng STMStudio. Tôi sẽ giải thích hàm TIMbase_Configuration() bắt đầu từ dòng 48 ở dòng này tham số TIM_Prescaler hiểu đơn giản như một bộ chia tần số. Tần số cao nhất mà clock timer 4 đạt đượ c là 84Mhz sau khi qua bộ chia này sẽ ra tần số clock timer(Fc_timer). Như VD trên tôi đã chọn Fc_timer = 1Mhz <=> Fc_timer = 84000000/84. Và TIM_Prescaler có công thức là: ((SystemCoreClock/2)/1000000)-1 = 83 , có lẽ bạn sẽ thắc mắc tại sao không phải là con số 84 mà l ại là 83, do hệ đếm bắt đầu từ 0 chứ không phải là 1 như chúng ta vẫn hay dùng để đếm số, bắt đầu đếm từ 0 -> 83 sẽ là 84 giá tr ị.
ɥuɐɥʇ ıoʇ@thanhduongvs
15
STM32F4
Dòng 49 : Period có nghĩa là chu kỳ của timer (không phải là chu k ỳ của 1 xung clock timer). Một chu k ỳ gồm 1000 xung clock mà mỗi xung clock = 1us ta sẽ đượ c period là 1ms. Tôi tr ừ đi cho 1 là vì hệ đếm bắt đầu từ 0 như đã giải thích bên trên. Dòng 50 : TIM_ClockDivision đượ c sử dụng ở trườ ng hợp nâng cao có liên quan đến dead-time và filters nên tôi sẽ không giải thích thêm, bạn nên gán TIM_ClockDivision = 0 như mặc định. Bạn chú ý là do timer 4 là timer 16bit nên các đố i số TIM_Prescaler và Period b ạn không thể gán giá tr ị lớ n
hơn 65535(0xFFFF). Dòng 51 : chọn mode counter đếm tăng, có nghĩa là mỗi xung nhị p timer, bộ đếm counter sẽ tự tăng lên một giá tr ị theo chiều dương cho đến khi nào bằng giá tr ị period sẽ đếm lại từ đầu, người ta thườ ng gọi trườ ng hợ p này là tràn bộ đếm. Dòng 53 : tôi thiết lậ p ngắt khi tràn bộ đếm có thông số TIM_IT_Update, còn r ất nhiều kiểu ngắt khác tùy vào nhu cầu của lậ p trình viên, bạn có thể tr ỏ đến định nghĩa để được rõ hơn.
Tôi xin đính kèm cho bạn hình bên dưới để bạn dễ hình dung hơn.
Ở hàm TIM2_Configuration() dòng từ 63 -> 74 tôi thiết lậ p một cấu hình cơ bản không ngắt, chú ý timer 2 là timer 32bit. Quay lại chương trình chính ở dòng 22 tôi dùng biến lasttime để đọc giá tr ị từ counter timer 2 ngay thời điểm lúc đó. Tiếp đó tôi thực hiện 2 lệnh GPIO_ToggleBits và Delay và tôi tiế p tục đọc giá tr ị counter timer 2 lưu vào biến nowtime. Hiệu của hai biến trên sẽ ra đượ c số clock mà 2 l ệnh trên thực hiện, từ đó bạn có thể suy ra thờ i gian thực hiện của 2 lệnh trên. Tôi tạo biến debug để bạn quan sát trên phần mềm STMStudio, bạn cần phải tham khảo bài viết về phần mềm này trướ c.
@04. Code trong bài vi ết: main.c 01 02 03 04 05 06
#include "stm32f4xx.h" GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
ɥuɐɥʇ ıoʇ@thanhduongvs
16
STM32F4
07 void GPIO_Configuration(void); 08 void TIMbase_Configuration(void); 09 void TIM2_Configuration(void); 10 void Delay(__IO uint32_t nCount); 11 12 volatile int32_t debug; 13 14 int main(void) 15 { 16 int32_t nowtime,lasttime; 17 GPIO_Configuration(); 18 TIMbase_Configuration(); 19 TIM2_Configuration(); 20 while (1) 21 { 22 lasttime = TIM_GetCounter(TIM2); 23 GPIO_ToggleBits(GPIOB,GPIO_Pin_0); 24 Delay(1000); 25 nowtime = TIM_GetCounter(TIM2); 26 debug = nowtime - lasttime; 27 } 28 } 29 30 void GPIO_Configuration(void) 31 { 32 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 33 /* Configure PB0 PB1 in output pushpull mode */ 34 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; 35 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 36 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 37 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 38 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; 39 GPIO_Init(GPIOB, &GPIO_InitStructure); 40 } 41 42 43 void TIMbase_Configuration(void) 44 { 45 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); 46 47 /* Time base configuration */ 48 TIM_TimeBaseStructure.TIM_Prescaler = ((SystemCoreClock/2)/1000000)-1; frequency = 1000000 49 TIM_TimeBaseStructure.TIM_Period = 1000 - 1; 50 TIM_TimeBaseStructure.TIM_ClockDivision = 0; 51 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 52 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 53 TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); 54 TIM_Cmd(TIM4, ENABLE); 55 56 NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; 57 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 58 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 59 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 60 NVIC_Init(&NVIC_InitStructure); 61 } 62 63 void TIM2_Configuration(void) 64 { 65 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 66
ɥuɐɥʇ ıoʇ@thanhduongvs
//
17
STM32F4
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
/* Time base configuration */ TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_Period = 0xFFFFFFFF; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_Cmd(TIM2, ENABLE); }
void Delay(__IO uint32_t nCount) { while(nCount--) { } }
#ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ while (1) {} } #endif
stm32f4xx_it.c 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#include "stm32f4xx_it.h" void NMI_Handler(void) { } void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) {} } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1) {} } void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) {} } void UsageFault_Handler(void)
ɥuɐɥʇ ıoʇ@thanhduongvs
18
STM32F4
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
{ /* Go to infinite loop when Usage Fault exception occurs */ while (1) {} } void DebugMon_Handler(void) {} void SVC_Handler(void) {} void PendSV_Handler(void) {} void SysTick_Handler(void) {}
void TIM4_IRQHandler(void) { static uint32_t time=0; if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) { if(++time>1000) { GPIO_ToggleBits(GPIOB,GPIO_Pin_1); time = 0; } TIM_ClearITPendingBit(TIM4, TIM_IT_Update); } }
ɥuɐɥʇ ıoʇ@thanhduongvs
19
STM32F4
#05 : PWM STM32F4 @01. Giớ i thiệu cơ bản về PWM: PWM điều chế độ r ộng xung (Pulse-width modulation) là ngoại vi phổ biến đượ c hỗ tr ợ hầu hết trong các loại vi điều khiển, ứng dụng r ộng rãi và nhất là trong lĩnh vực điều khiển động cơ. Đơn giản nhất để hiểu PWM tôi VD cho bạn như sau : cấu hình out put cho chân GPIO điều khiển bóng LED, nếu bạn muốn làm sáng bóng LED theo hiệu ứng mờ hay tỏ, giải pháp đó là bạn sẽ cho bóng LED sáng t ắt ở tần số cao sao cho mắt bạn mất khả năng phân tích sự sáng tắt của một bóng LED. Bạn muốn bóng LED sáng tỏ thì thờ i gian sáng sẽ nhiều hơn thờ i gian tắt, và ngượ c lại. Trên đây chỉ là ví dụ cơ bản nhất để bạn hiểu như thế nào là PWM, bạn vui lòng xem hình ảnh bên dưới để hiểu rõ hơn.
Như trên hình đã rất rõ - Duty cycle là t ỷ lệ phần trăm mức cao. - Period là chu k ỳ xung. - Pulse width là giá tr ị mức cao so vớ i period. Dựa trên nguyên lý bên trên mà trong STM32 hay các loại vi điều khiển khác điều hỗ tr ợ bộ tạo độ r ộng xung tự động mà bạn không phải tốn thờ i gian bật tắt chân IO, có thể thiết lậ p chu k ỳ, tần số, độ r ộng xung và một số chức năng đặc biệt. PWM thuộc khối timer.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối phần cứng như hình bên dướ i, nếu có thể bạn thay các bóng LED b ằng oscilloscope để hiểu rõ hơn về quá trình phát xung, chu k ỳ, tần số của PWM.
ɥuɐɥʇ ıoʇ@thanhduongvs
20
STM32F4
@03. Lập trình PWM cơ bả n: Trong bài tôi thiết lậ p PWM1-2-3 và 3 kênh đảo của nó tổng cộng 6 kênh PWM xuất ra môi trườ ng. Tôi sẽ giải thích hàm TIM_PWM_Configuration() dòng 19 -> 71
Trong bài Timer cơ bản vớ i STM32F4 tôi đã thiết lập timer cơ bản, PWM cũng thuộc khối timer nên PWM sẽ sử dụng period, prescaler, counter c ủa timer và các khái niệm này tôi đã giải thích r ất k ỹ ở bài trướ c. Dòng 25 : chọn sử dụng Mode AF (mode tính năng phụ tr ợ) cho chân IO, mode này không phải là mode In hay Out mà do ngoại vi đó tự động thiết lậ p In-Out. Dòng 34 -> 39 bạn phải k ết nối chân IO đến khối timer 1 cho đúng, bạ n xem datasheet phần ―Table 6. STM32F40x pin and ball definitions‖ trang 45 để rõ sơ đồ bố trí chân của STM32F405RGT(64 Pin)
ɥuɐɥʇ ıoʇ@thanhduongvs
21
STM32F4
Dòng 49 : Chọn mode PWM1, trong timer có nhi ều mode mà tùy theo ứng dụng để bạn lựa chọn mode cho phù hợ p. Từ OC mà bạn thấy trong dòng khai báo là viết tắt của từ Output Compare. Dòng 50 : Cho phép chân PWM hoạt động. Dòng 51 : Chọn cực mức cao cho đầu ra, nếu bạn chọn mức thấ p thì nó sẽ bị đảo ngượ c hình dạng của đầu ra, Chu k ỳ, tần số đều như nhau. Dòng 52 - 53 : tương tự như dòng 50-51 nhưng khai báo cho kênh đảo có nghĩa là chân đượ c ký hiệu thêm chữ ―N‖. Bạn cần lưu ý do vốn dĩ kênh PWM và kênh đảo của nó đã được định nghĩa trong chip là trạng thái đảo của nhau nên ở dòng 50-51 và 52-53 tôi khai báo giống nhau và nó sẽ tự đảo không cần bạn phải can thiệp đảo lại các cực của PWM kênh N. Sau khi thiết lậ p xong các thông số bạn phải truyền biến cấu trúc xuống cho hàm TIM_OC1Init() để thiết lậ p cho kênh PWM1, vẫn giữ lại các thông số đó, tương tự truyền cho PWM2 và PWM3. Dòng 58-61-64-66 : bật chức năng preload cho OC1, OC2 và OC3.
Bướ c cuối cùng trong hàm TIM_PWM_Configuration() bạn phải bật counter để bắt đầu hoạt động timer. Ở dòng 70 do tôi sử dụng timer1 là timer đặc biệt nên bạn phải thực hiện bướ c này. Sử dụng các timer khác tr ừ timer1 và timer8 thì bạn không cần bướ c này. Quay lại chương trình chính từ dòng 12 -> 14 tôi gán giá tr ị vào thanh ghi chứa Pulse width của PWM1-PWM2PWM3 tương đương vớ i 10-50-90% duty cycle.
ɥuɐɥʇ ıoʇ@thanhduongvs
22
STM32F4
@04. Code trong bài vi ết: main.c 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22
#include "stm32f4xx.h" TIM_TimeBaseInitTypeDef TIM_OCInitTypeDef GPIO_InitTypeDef
TIM_TimeBaseStructure; TIM_OCInitStructure; GPIO_InitStructure;
void TIM_PWM_Configuration(void); int main(void) { TIM_PWM_Configuration(); TIM1->CCR1 = 10 * 65535 / 100; TIM1->CCR2 = 50 * 65535 / 100; TIM1->CCR3 = 90 * 65535 / 100; while (1) {} }
// 10% Duty cycle // 50% Duty cycle // 90% Duty cycle
void TIM_PWM_Configuration(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE);
ɥuɐɥʇ ıoʇ@thanhduongvs
23
STM32F4
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinAFConfig(GPIOA, GPIO_PinAFConfig(GPIOA, GPIO_PinAFConfig(GPIOA, GPIO_PinAFConfig(GPIOB, GPIO_PinAFConfig(GPIOB,
GPIO_PinSource7, GPIO_AF_TIM1); GPIO_PinSource8, GPIO_AF_TIM1); GPIO_PinSource9, GPIO_AF_TIM1); GPIO_PinSource10, GPIO_AF_TIM1); GPIO_PinSource0, GPIO_AF_TIM1); GPIO_PinSource1, GPIO_AF_TIM1);
/* Time base configuration */ TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 65535 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OCInitStructure.TIM_Pulse = 0; //TIM_OCStructInit(&TIM_OCInitStructure); TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2Init(TIM1, &TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC3Init(TIM1, &TIM_OCInitStructure); TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); /* TIM1 enable counter */ TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); } #ifdef
USE_FULL_ASSERT
/** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) {
ɥuɐɥʇ ıoʇ@thanhduongvs
24
STM32F4
84 85 86 87 88 89 90 91 92
/* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ while (1) {} } #endif
ɥuɐɥʇ ıoʇ@thanhduongvs
25
STM32F4
#06 : ADC & DMA STM32F4 @01. Giớ i thiệu ADC & DMA: Các tín hiệu mà bạn thườ ng gặ p trong tự nhiên chẳng hạn như điện áp, ánh sáng, âm thanh, nhiệt độ … đều tồn tại dướ i dạng tương tự có nghĩa là tín hiệu liên tục và mức độ chia nhỏ vô hạn. VD như trong khoảng điệ n áp từ 0 -> 5V thì bạn sẽ có vô số khoảng giá tr ị điện áp, ánh sáng sẽ tồn tại từ mờ cho tớ i sáng tỏ, âm thanh từ nhỏ cho đến lớ n. Ngượ c lại trong vi điều khiển chỉ có khái niệm số (digital), cấu trúc từ nhân cho đến bộ nhớ hoạt động dựa trên các bóng bán dẫn chỉ gồm mức 0-1 nên bạn muốn giao tiế p vớ i chip thì bạn phải số hóa trước khi đưa vào chip. Quá trình số hóa có thể thực hiện bằng nhiều cách và nhiều công đoạn nhưng mục đích cuối cùng là để vi điều khiển hiểu đượ c tín hiệu tương tự đó. ADC (analog-to-digital converter) bộ chuyển đổi tín hiệu tương tự-số là thuật ngữ nói đến sự chuyển đổi một tín hiệu tương tự thành tín hiệu số hóa để dùng trong các hệ số(digital) hay vi điều khiển. Trong STM32 có hỗ tr ợ ADC chuyển đổi tín hiệu điện áp thành tín hiệu số với độ phân giải 12bit. Tôi sẽ cho bạn một VD để bạn hiểu quá trình số hóa trong STM32 diễn ra như thế nào, giả sử tôi cần đo điện áp tối thiểu là 0V và tối đa là 3.3V, trong STM32 sẽ chia 0->3.3V thành 4096 khoảng giá tr ị (từ 0 -> 4095), khi que đo từ chân IO đọc được 0 thì tương đương 0V, đọc đượ c 2047 tương đương 1.65V và đọc được 4095 tương đương 3.3V. Đây chỉ là một ví dụ cơ bản, trên thực tế ADC là một ngoại vi thiết yếu của dòng STM32 nên gồm r ất nhiều chức năng, bạ n cần kiến thức nền tản để có thể đọc hiểu các chức năng này.
Ngoài ra để hỗ tr ợ quá trình chuyển đổi của ADC một cách liên tục tôi xin giớ i thiệu vớ i bạn khái niệm về DMA. DMA (Direct memory access) là m ột k ỹ thuật chuyển dữ liệu từ bộ nhớ đến ngoại vi hoặc từ ngoại vi đến bộ nhớ mà không yêu cầu đến sự thực thi của CPU, có nghĩa là CPU sẽ hoàn toàn độc lậ p vớ i ngoại vi đượ c hỗ tr ợ DMA mà vẫn đảm bảo ngoại vi thực hiện công việc đượ c giao, tùy vào từng loại ngoại vi mà DMA có sự hỗ tr ợ khác nhau.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối như hình bên dướ i
ɥuɐɥʇ ıoʇ@thanhduongvs
26
STM32F4
@03. Lập trình ADC cơ bả n: Tôi sẽ giải thích cho bạn hàm ADC_Config() từ dòng 22 -> 85, trong hàm này tôi thiết lậ p DMA và ADC cho 3 chân PC0-PC1-PC2. Dòng 29 : chọn mode analog cho chân IO.
Để tiế p cận và hiểu đượ c cách hoạt động của DMA bạn cần phải biết một số khái niệm cơ bản trong DMA, bạn có thể tham khảo khái quát trong tài liệu Reference manual trang 211. Bài viết sử dụng ADC1 mà ADC1 chỉ đượ c hỗ tr ợ bở i DMA2, nằm ở stream 0 – channel 0, bạn tham khảo bảng 34 ―DMA2 request mapping‖ trang 217.
ɥuɐɥʇ ıoʇ@thanhduongvs
27
STM32F4
Dòng 33 : dựa vào bảng trên channel đượ c hỗ tr ợ là channel 0. Dòng 34 : biến DMA_Memory0BaseAddr sẽ chứa địa chỉ của biến ADCValue mà tôi đã khai báo ở dòng 8 bên trên. Ngoài ra tôi còn ép kiểu để biến ADCValue cùng kiểu vớ i biến DMA_Memory0BaseAddr . Mục đích dòng này là mỗi khi có một sự truyền nhận dữ liệu của DMA dữ liệu lậ p tức đượ c gán tự động vào biến này, hoặc tự động truyền biến này đến ngoại vi.
Dòng 35 : gán địa chỉ của thanh ghi chứa giá tr ị chuyển đổi ADC vào biến DMA_PeripheralBaseAddr của DMA. Dòng 36 : chọn hướ ng chuyển dữ liệu từ ngoại vi đến bộ nhớ .
Ở 3 dòng bên sẽ hoạt động như sau, khi có một sự kiện chuyển đổi ADC xảy ra, ngay lậ p tức DMA sẽ lấy dữ liệu từ thanh ghi chứa dữ liệu ADC và gán vào bi ến ADCValue, việc làm này hoàn toàn tự động và độc lậ p vớ i xử lý của CPU, hướ ng dữ liệu chuyển đượ c chọn ở dòng 36. Dòng 37 : kích thướ c của mảng dữ liệu, ở đây có nghĩa là số phần tử của mảng ADCValue. Dòng 38 : bạn disable mode này do nếu bạn bật mode này thì mỗi lần chuyển dữ liệu thì địa chỉ ngoại vi sẽ tăng dần, điều này là không cần thiết và r ất nguy hiểm nếu như bạn không nắm rõ địa chỉ tr ỏ đến tiế p theo. Dòng 39 : bạn cần enable mode này, mỗi khi chuyển đổi xảy ra bạn cần tăng địa chỉ bộ nhớ của bạn do bên trên biến ADCValue có đến 3 phần tử, nếu không tăng địa chỉ lên thì chỉ duy nhất có biến ADCValue[0] là có dữ liệu. Dòng 40 : chọn kích thướ c thanh ghi chứa dữ liệu của ngoại vi là HalfWord (kiểu 16bit), ngoài ra còn có kiểu Byte (8bit), kiểu Word(32bit). Dòng 41 : chọn kích thướ c mảng dữ liệu ADCValue là HalfWord. Dòng 42 : chọn mode DMA chế độ vòng tròn, có nghĩa là việc chuyển đổi liên tục lặ p lại. Dòng 43 : thiết lậ p chế độ ưu tiên cao.
ɥuɐɥʇ ıoʇ@thanhduongvs
28
STM32F4
Dòng 55 : chọn mode cho ADC, mode này là mode đơn cơ bản không sử dụng chức năng đặc biệt, có r ất nhiều mode trong ADC, nhưng bài viế t hôm nay thì bạn sử dụng mode này. Dòng 56 : thiết lậ p bộ chia 2, do tôi sử dụng DMA nên không xảy ra ngắt, sẽ không tốn thờ i gian thực thi CPU nên tốt nhất cho ADC lấy mẫu ở tần số cao nhất. Dòng 58 : thờ i gian tr ễ giữa 2 lần lấy mẫu. Dòng 61 : chọn độ phân giải ADC là 12bit. Dòng 64 : không sử dụng chế độ convert bằng tín hiệu bên ngoài. Dòng 65 : canh lề dữ liệu lệch phải, do thanh ghi ADC là 16bit nhưng độ phân giải chỉ 12bit nên 12bit dữ liệu này sẽ đượ c canh lề về bên trái hoặc bên phải tùy ngườ i sử dụng. Dòng 66 : số kênh ADC chuyển đổi. Dòng 70 - 72 : đối số thứ 2 trong hàm ADC_RegularChannelConfig bạn tham khảo datasheet trang 46, đối số thứ 3 tôi sắ p xế p theo thứ tự tăng dần 1-2-3 có nghĩa là mỗi lần xảy ra chuyển đổi các kênh sẽ theo thứ tự mà chuyển đổi bắt đầu từ số 1. Đối số thứ 3 chính là thờ i gian lấy mẫu của ADC, số càng lớ n thờ i gian lấy mẫu càng lâu.
Quay lại chương trình chính, bạn cần sử dụng phần mềm STMStudio để debug quan sát cả 3 phần tử của mảng ADCValue. Quá trình lấy mẫu ADC hoàn toàn độc lậ p vớ i CPU, cứ xảy ra sự kiện chuyển đổi thì DMA tự động gán giá tr ị chuyển đổi đó vào mảng ADCValue.
ɥuɐɥʇ ıoʇ@thanhduongvs
29
STM32F4
@04. Code trong bài vi ết: main.c 001 #include "stm32f4xx.h" 002 003 DMA_InitTypeDef DMA_InitStructure; 004 ADC_InitTypeDef ADC_InitStructure; 005 ADC_CommonInitTypeDef ADC_CommonInitStructure; 006 GPIO_InitTypeDef GPIO_InitStructure; 007 008 volatile uint16_t ADCValue[3]={0}; 009 010 void ADC_Config(void); 011 012 int main(void) 013 { 014 ADC_Config(); 015 while (1) 016 {
ɥuɐɥʇ ıoʇ@thanhduongvs
30
STM32F4
017 018 } 019 } 020 021 022 void ADC_Config(void) 023 { 024 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); 025 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 026 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); 027 028 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; 029 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; 030 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; 031 GPIO_Init(GPIOC, &GPIO_InitStructure); 032 033 DMA_InitStructure.DMA_Channel = DMA_Channel_0; 034 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCValue; 035 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(ADC1->DR)); 036 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; 037 DMA_InitStructure.DMA_BufferSize = 3; 038 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 039 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 040 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 041 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; 042 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 043 DMA_InitStructure.DMA_Priority = DMA_Priority_High; 044 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; 045 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; 046 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; 047 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; 048 DMA_Init(DMA2_Stream0, &DMA_InitStructure); 049 050 /* DMA2_Stream0 enable */
ɥuɐɥʇ ıoʇ@thanhduongvs
31
STM32F4
051 DMA_Cmd(DMA2_Stream0, ENABLE); 052 053 054 /* ADC Common Init **********************************************************/ 055 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; 056 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; 057 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 058 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; 059 ADC_CommonInit(&ADC_CommonInitStructure); 060 061 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; 062 ADC_InitStructure.ADC_ScanConvMode = ENABLE; 063 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 064 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; 065 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 066 ADC_InitStructure.ADC_NbrOfConversion = 3; 067 ADC_Init(ADC1, &ADC_InitStructure); 068 069 /* ADC1 regular channels configuration */ 070 ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_3Cycles); 071 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_3Cycles); 072 ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_3Cycles); 073 074 /* Enable ADC1 DMA */ 075 ADC_DMACmd(ADC1, ENABLE); 076 077 /* Enable DMA request after last transfer (Single-ADC mode) */ 078 ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); 079 080 /* Enable ADC1 */ 081 ADC_Cmd(ADC1, ENABLE); 082 083 /* Start ADC1 Software Conversion */ 084 ADC_SoftwareStartConv(ADC1);
ɥuɐɥʇ ıoʇ@thanhduongvs
32
STM32F4
085 } 086 087 088 void Delay(__IO uint32_t nCount) 089 { 090 while(nCount--) 091 { 092 } 093 } 094 095 096 #ifdef USE_FULL_ASSERT 097 void assert_failed(uint8_t* file, uint32_t line) 098 { 099 /* User can add his own implementation to report the file name and line number, 100 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 101 102 while (1) 103 {} 104 } 105 #endif 106
ɥuɐɥʇ ıoʇ@thanhduongvs
33
STM32F4
#07 : USART STM32F4 @01. Giớ i thiệu USART trong STM32: USART (Universal synchronous asynchronous receiver transmitter) truyền nhận nối tiếp đồng bộ và không đồng bộ. Trong truyền thông nối tiế p dữ liệu truyền sẽ nối đuôi nhau trên một hay vài đườ ng truyền. Ưu điểm của truyền thông nối tiếp là vi điều khiển có khả năng truyền-nhận nhiều dữ liệu, tiết kiệm đường đường IO, nhưng nhược điể m là không được nhanh như truyề n song song và dễ bị mất, lỗi dữ liệu, phải có k ế hoạch phòng ngừa các tình huống này.
Chuẩn truyền thông này tương thích vớ i RS232 của PC nếu có một thiết bị chuyển đổi điện áp để giao tiế p. Nếu máy tính bạn không có cổng COM bạn có thể sử dụng thiết bị chuyển đổi ―USB to COM‖ hoặc ―USB to USART‖.
Dưới đây là khung truyền chuẩn của USART và RS232.
ɥuɐɥʇ ıoʇ@thanhduongvs
34
STM32F4
@02. K ết nối phần cứ ng: Để giao tiế p vớ i máy tính ta cần một thiết bị k ết nối vớ i máy tính qua chuẩn RS232 hoặc USB to USART, tôi giớ i thiệu vớ i bạn mạch "usb to uart breakout board" Sơ đồ nguyên lý, layout, driver tôi đã đính kèm bên trên. Bạn cũng có thể mua sản phẩm ở một số trang cung cấ p linh kiện điện tử để tiết kiệm đượ c thờ i gian làm mạch.
ɥuɐɥʇ ıoʇ@thanhduongvs
35
STM32F4
Sau khi lắ p ráp board và cài driver hoàn chỉnh, bạn vui lòng k ết nối như sơ đồ bên dướ i. Chú ý k ết nối theo kiểu đấu chéo TX này vào RX kia.
@03. Lập trình USART cơ bản: Trong bài viết ngày hôm nay tôi k ết hợp thư viện chuẩn của C với UART để truyền nhận dữ liệu, nên ở dòng 2 tôi khai báo thư viện stdio.h, nếu bạn nào đã từng lậ p trình C trên PC chắc hẳn thư viện này r ất quen thuộc để sử dụng các hàm liên quan đến xuất nhậ p (printf, scanf). Tiếp đến tôi sẽ giải thích cho bạn hàm USART_Configuration() từ dòng 39 -> 69
Dòng 57 : BaudRate là đối số tôi truyền từ khai báo ở đầu hàm USART_Configuration(), nó có nghĩa là tốc độ baud (số bit truyền đi trong 1s). Chẳng hạn tôi cho đối số BaudRate là 9600 vậy trong 1s truyền đi 9600 bit và chu kỳ mỗi bit là có thờ i gian là 1/9600 = 1.04^-4s. Đối số này càng cao thờ i gian truyền dữ liệu càng nhanh nhưng độ an toàn lại càng giảm, ngoài ra bạn nên sử dụng tốc độ baud vớ i một quy chuẩn quốc tế (chẳng hạn 4800, 9600, 19200, 38400 …) Dòng 58 : độ dài khung truyền là 8bit. Dòng 59 : 1 stop bit.
ɥuɐɥʇ ıoʇ@thanhduongvs
36
STM32F4
Dòng 60 : không kiểm tra chẵn lẽ. Dòng 62 : chọn chế độ truyền và nhận. Dòng 65-66 : k ết nối chân IO đến khối USART1. Dòng 68 : cho phép USART1 bắt đầu hoạt động. Quay lại với chương trình chính ở dòng 30 tôi sử dụng hàm printf hàm này là hàm xuất dữ liệu nên sẽ liên quan đến thư viện phần cứng của USART trong STM32. Mỗi khi hàm printf hoạt động sẽ nhảy đến hàm PUTCHAR_PROTOTYPE ở dòng 96 mà đã đượ c khai báo sử dụng ở dòng 16 bên trên. Dòng 100 : hàm USART_SendData() sẽ gửi đi 1 ký tự UART. Dòng 103 : tôi thực hiện một vòng lặ p mà khi k ết thúc quá trình truyền đi 1 ký tự sẽ tự động thoát khỏi vòng lặ p này. Quay lại dòng 34-35 ở chương trình chính : tôi sẽ nhậ p một số thực kiểu float từ máy tính và gán vào biến a bằng hàm scanf, sau đó lại tiế p tục truyền biến a lên máy tính bằng hàm printf. Bài viết đượ c tôi build trên trình biên dịch IAR, trong trình dịch Keil hàm scanf không nh ậ p ký tự vào đượ c, hiện tại tôi vẫn chưa có hướ ng giải quyết vớ i hàm scanf của Keil mong phiên bản sau Keil sẽ fix lỗi này. Phần thiết lậ p chuẩn USART trong hàm USART_Configuration() như thế nào thì phần mềm trên PC bạn phải thiết lập tương tự để cả hai phía được đồng bộ vớ i nhau
ɥuɐɥʇ ıoʇ@thanhduongvs
37
STM32F4
Bên dưới đây là video demo quá trình truyền nhận từ máy tính bằng phần mềm Terminal đến board MiniTestF4. Bạn chú ý, trên video lúc tôi nhậ p dữ liệu vào sẽ dư ra 1 số 0 đàng trướ c mỗi số nhậ p vào, do lỗi trình dịch làm mất ký tự đầu khi nhận. Vd muốn nhậ p vào số 0.12345 thì bạn phải thêm trướ c số này một số bất k ỳ như tôi là 00.12345.
@04. Code trong bài vi ết: main.c 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050
#include "stm32f4xx.h" #include USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; void USART_Configuration(unsigned int BaudRate); void SendUSART(USART_TypeDef* USARTx,uint16_t ch); int GetUSART(USART_TypeDef* USARTx); /* Private function prototypes -----------------------------------------------*/ #ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #define GETCHAR_PROTOTYPE int fgetc(FILE *f) #endif /* __GNUC__ */ void Delay(__IO uint32_t nCount) { while(nCount--) { } } int main(void) { USART_Configuration(38400); printf(" Test\r"); while (1) { float a; scanf("%f",&a); printf("%3.5f\r",a); } } void USART_Configuration(unsigned int BaudRate) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* Configure USART Tx as alternate function */ GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);
ɥuɐɥʇ ıoʇ@thanhduongvs
38
STM32F4
051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111
/* Configure USART Rx as alternate function */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOB, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = BaudRate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_USART1); GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_USART1); USART_Cmd(USART1, ENABLE); } void SendUSART(USART_TypeDef* USARTx,uint16_t ch) { USART_SendData(USARTx, (uint8_t) ch); /* Loop until the end of transmission */ while (USART_GetFlagStatus(USARTx, USART_IT_TXE) == RESET) {} } int GetUSART(USART_TypeDef* USARTx) { while (USART_GetFlagStatus(USARTx, USART_IT_RXNE) == RESET) {} return USART_ReceiveData(USARTx); } /** * @brief Retargets the C library printf function to the USART. * @param None * @retval None */ GETCHAR_PROTOTYPE { return GetUSART(USART1); } PUTCHAR_PROTOTYPE { /* Place your implementation of fputc here */ /* e.g. write a character to the USART */ USART_SendData(USART1, (uint8_t) ch); /* Loop until the end of transmission */ while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} return ch; } #ifdef
USE_FULL_ASSERT
/**
ɥuɐɥʇ ıoʇ@thanhduongvs
39
STM32F4
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
* @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
ɥuɐɥʇ ıoʇ@thanhduongvs
40
STM32F4
#08 : SPI STM32F4 @01. Giớ i thiệu truyền thông SPI: SPI (tiếng Anh: Serial Peripheral Interface, SPI bus — Giao diện Ngoại vi Nối tiế p, bus SPI) là một chuẩn đồng bộ nối tiếp để truyền dữ liệu ở chế độ song công toàn phần full-duplex (hai chiều, hai phía), do công ty Motorola thiết k ế nhằm đảm bảo sự liên hợ p giữa các vi điều khiển và thiết bị ngoại vi một cách đơn giản và giá r ẻ. Đôi khi SPI còn đượ c gọi là giao diện bốn-dây (tiếng Anh: four-wire).
Trong giao diện SPI có sử dụng bốn tín hiệu số: MOSI hay SI — cổng ra của bên chủ động, cổng vào của bên bị động (tiếng Anh: Master Out Slave In), dành cho vi ệc truyền dữ liệu từ thiết bị chủ động đến thiết bị bị động. MISO hay SO — cổng vào của bên chủ động, cổng ra của bên bị động (tiếng Anh: Master In Slave Out), dành cho việc truyền dữ liệu từ thiết bị bị động đến thiết bị chủ động. SCLK hay SCK — tín hiệu đồng hồ tăctơ nối tiế p (tiếng Anh: Serial Clock), dành cho vi ệc truyền tín hiệu đồng hồ tăctơ dành cho thiết bị bị động. CS hay SS — chọn vi mạch, chọn bên bị động (tiếng Anh: Chip Select, Slave Select).
Trong đó nếu là chip master sẽ có quyền quyết định xung nhị p SCK (hay chủ động nguồn xung nhị p SCK). Ở chế độ song công toàn phần thì trong cùng một thời điểm cả hai thiết bị đều có thể phát và nhận dữ liệu, đây là một ưu thế r ất lớ n của chuẩn truyền thông này.
Ngoài ra chip master có thể giao tiế p vớ i nhiều chip slave trong cùng mạng và có nhiều phương pháp khác nhau.
ɥuɐɥʇ ıoʇ@thanhduongvs
41
STM32F4
Ở phương pháp này chip master cần nhiều đườ ng SS, có thể thay thế bằng đường IO thông thườ ng. Trong một thờ i điểm chỉ nên giao tiế p vớ i một chip slave để tránh trườ ng hợp các chip slave đẩy dữ liệu về cùng lúc sẽ gây lỗi dữ liệu trên đườ ng MISO. Bạn có thể tham khảo phương pháp như hình
bên dưới đượ c gọi là "Daisy chain".
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối như sơ đồ bên dướ i
ɥuɐɥʇ ıoʇ@thanhduongvs
42
STM32F4
@03. Lập trình SPI cơ bản: Trong bài hôm nay tôi sử dụng 2 bộ SPI, một cho master và một cho slave. Phần master thì tôi sử dụng ở chế độ thông thườ ng, không ngắt, không DMA. Riêng slave tôi sử dụng ngắt nhận, do thời điểm truyền dữ liệu của master là không biết trướ c nên cần dùng ngắt để tránh mất dữ liệu. Bắt đầu tôi xin giải thích hàm SPI_Configuration(), trong hàm này thiết lậ p SPI cho cả master và slave. Dòng 54 : chọn chế độ truyền dữ liệu song công full-duplex, điều này có thể không cần thiết do trong ví dụ này tôi chỉ dùng master để truyền mà không nhận dữ liệu, nhưng bạn cũng có thể để nguyên như trên và không quan tâm đến chế độ nhận. Dòng 55 : chọn mode master cho SPI1. Dòng 56 : chọn kích thướ c của khung truyền là 8bit. Dòng 57-58 : ở hai dòng lệnh này bạn cần tham khảo hình bên dưới để nắm rõ dữ liệu đượ c lấy mẫu ở cạnh và pha như thế nào.
ɥuɐɥʇ ıoʇ@thanhduongvs
43
STM32F4
Dòng 59 : chọn sử dụng chân SS bằng phần mềm, trên thực tế bạn có thể không cần quan tâm do bên trên tôi đã khai báo một chân IO (PA4) làm chức năng SS. Dòng 60 : bộ chia tần số của SPI, sau khi qua bộ chia này ta sẽ suy ra đượ c xung nhị p trên chân SCK. Do ta sử dụng SPI1 thuộc khối APB2 nên tốc độ tối đa mà ngoại vi trên khối này đạt đượ c là 84MHz. Tôi chọn bộ chia 256 vậy tần số tối đa mà chân SCK đạt đượ c là 84MHz/256 = 328.125KHz. Dòng 61 : bit truyền đi đầu tiên là bit MSB có nghĩa là bit có trọ ng số cao nhất bit thứ 8.
Ở phần khai báo cho slave cũng tương tự như khai báo cho master, nhưng mode hoạt động là mode slave và thiết lậ p thêm ngắt khi quá trình nhận dữ liệu hoàn tất như dòng 95. K ết thúc hàm này bạn cho cả hai khối SPI này hoạt động.
Ở chương trình chính dòng 25 tôi sử dụng 1 macro SS_DIS đã được tôi định nghĩa ở dòng 9, việc làm này của tôi sẽ r ất tiện lợ i nếu như bạn hay thay đổ i các chân IO, b ạn chỉ việc thay đổi bên trên mà không cần thay đổi toàn bộ chương trình của bạn đang viết. Dòng 26 : tôi sử dụng hàm SPI_I2S _SendData() để gửi dữ liệu từ master lên đườ ng truyền SPI, đối số SPI_data_send chính là biến dữ liệu cần truyền đi, bạn cần sử dụng bộ phần mềm STMStudio để quan sát biến này. Dòng 27 : vòng lặp này để bẫy chương trình, khi kế t thúc quá trình truyền thì sẽ thoát khỏi vòng lặ p. Việc này để tránh trườ ng hợ p tốc độ vi điều khiển quá nhanh, dữ liệu cũ còn chưa truyền đi hết thì dữ liệu mới đã đượ c gửi tiế p. Mục đích sử dụng delay ở dùng 29 là để trì hoãn khoản mức cao mà master t ạo ra trên SS, n ếu không trì hoãn thì chương trình quay lại dòng 25 làm slave chưa kị p phát hiện ra tín hiệu mức cao. Khi có sự kiện nhận dữ liệu trên slave lặ p tức chương trình sẽ nhảy đến hàm ngắt SPI2_IRQHandler() ở dòng 59 trong file stm32f4xx_it.c. Tôi tiế p tục giải thích trên file stm32f4xx_it.c
Ở dòng 4 tôi khai báo thêm từ khóa extern để main.c và stm32f4xx_it.c sử dụng chung biến SPI_data_get. Dòng 63 : khi có một tín hiệu ngắt do quá trình nhận đã hoàn tất tôi sẽ dùng hàm SPI_I2S_ReceiveData() lấy dữ liệu trong thanh ghi ra và gán vào bi ến SPI_data_get. Tiế p theo bạn mở phần mềm STMStudio và quan sát 2 biến SPI_data_send, SPI_data_get. Biến SPI_data_send sẽ đượ c bạn gán giá tr ị truyền đi vào và biến SPI_data_get để bạn kiểm tra xem đúng như biến mình truyền đi chưa.
ɥuɐɥʇ ıoʇ@thanhduongvs
44
STM32F4
@04. Code trong bài vi ết: main.c 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024
#include "stm32f4xx.h" /* MASTER SLAVE PA4 --- CONTROL_SS PB12 --- SPI2_SS PA5 --- SPI1_SCK PB13 --- SPI2_SCK PA6 --- SPI1_MISO PB14 --- SPI2_MISO PA7 --- SPI1_MOSI PB15 --- SPI2_MOSI */ #define SS_DIS GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define SS_EN GPIO_SetBits(GPIOA, GPIO_Pin_4) volatile uint8_t SPI_data_send,SPI_data_get; GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; void SPI_Configuration(void); void Delay(__IO uint32_t nCount); int main(void) { SPI_Configuration(); while (1) {
ɥuɐɥʇ ıoʇ@thanhduongvs
45
STM32F4
025 SS_DIS; 026 SPI_I2S_SendData(SPI1, SPI_data_send); 027 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); 028 SS_EN; 029 Delay(1000); 030 } 031 } 032 033 void SPI_Configuration(void) 034 { 035 /* SPI_MASTER configuration ------------------------------------------------*/ 036 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 037 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); 038 039 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 040 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 041 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 042 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 043 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; 044 GPIO_Init(GPIOA, &GPIO_InitStructure); 045 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; 046 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 047 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 048 GPIO_Init(GPIOA, &GPIO_InitStructure); 049 050 GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1); 051 GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1); 052 GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1); 053 054 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 055 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; 056 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 057 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; 058 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 059 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 060 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; 061 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 062 SPI_InitStructure.SPI_CRCPolynomial = 7; 063 SPI_Init(SPI1, &SPI_InitStructure); 064 065 /* SPI_SLAVE configuration ------------------------------------------------*/ 066 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 067 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); 068 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; 069 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 070 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 071 GPIO_Init(GPIOB, &GPIO_InitStructure); 072 073 GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_SPI2); // only connect to 074 GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2); // only connect to 075 GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_SPI2); // only connect to 076 GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_SPI2); // only connect to 077 078 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 079 SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; 080 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 081 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; 082 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 083 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 084 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
ɥuɐɥʇ ıoʇ@thanhduongvs
46
STM32F4
085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI2, &SPI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE); /* Enable SPI_SLAVE */ SPI_Cmd(SPI2, ENABLE); /* Enable SPI_MASTER */ SPI_Cmd(SPI1, ENABLE); } void Delay(__IO uint32_t nCount) { while(nCount--) { } } #ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
stm32f4xx_it.c 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
#include "stm32f4xx_it.h" #include "stm32f4xx.h" extern uint8_t SPI_data_get; void NMI_Handler(void) { } void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { } } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */
ɥuɐɥʇ ıoʇ@thanhduongvs
47
STM32F4
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
while (1) { } } void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) { } } void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) { } } void SVC_Handler(void) { } void DebugMon_Handler(void) { } void PendSV_Handler(void) { } void SysTick_Handler(void) { } void SPI2_IRQHandler(void) { if (SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_RXNE) != RESET) { SPI_data_get = SPI_I2S_ReceiveData(SPI2); //SPI_I2S_ClearFlag(SPI2, SPI_I2S_IT_RXNE); } }
ɥuɐɥʇ ıoʇ@thanhduongvs
48
STM32F4
#09 : I2C STM32F4 @01. Giớ i thiệu truyền thông I2C: I2C (inter-integrated circuit) là chuẩn truyền thông nối tiế p 2 dây gồm 1 dây xung clock(SCL) và 1 dây dữ liệu (SDA). Các chip chủ-tớ đượ c nối chung với nhau trên hai đường dây này và đượ c nối với điện tr ở treo.
@02. K ết nối phần cứ ng: Bạn vui lòng k ết nối như bên dướ i
ɥuɐɥʇ ıoʇ@thanhduongvs
49
STM32F4
@03. Lập trình I2C cơ bản: Trong bài viết hôm nay tôi sử dụng 2 bộ I2C, I2C1 vai trò là chip master, I2C2 là chip slave. Master truyền dữ liệu theo kiểu thông thườ ng không thiết lậ p ngắt và DMA, riêng slave thiết lậ p ngắt sự kiện.
Đầu tiên tôi sẽ giải thích hàm I2C_Configuration() từ dòng 35 ->102. Dòng 37 –> 43 : đây là các đoạn mã tiền xử lý trong C, điển hình ở dòng 37 #ifdef FAST_I2C_MODE có nghĩa là nếu như macro FAST_I2C_MODE được định nghĩa thì sẽ thực hiện 2 dòng 38-39, nếu FAST_I2C_MODE chưa đượ c định nghĩa thì sẽ thực hiện 2 dòng 40-41. Ở dòng 8 tôi đã định nghĩa sử dụng FAST_I2C_MODE.
Dòng 51 : để sử dụng I2C thì bạn phải chọn mode OD (open drain) cấu hình cho GPIO. Dòng 52 : do k ết nối phần cứng tôi không sử dụng điện tr ở treo lên mức cao nên tôi phải cấu hình điện tr ở treo nội lên mức cao. Dòng 63 : lệnh này dùng reset lại các thanh ghi của khối I2C đưa về mặc định. Dòng 64 : chọn mode hoạt động là I2C, ngoài ra còn một số chuẩn khác dựa trên chuẩn I2C như SMBus. Dòng 65 : chọn tỉ lệ mức thấ p/mức cao của xung clock. Macro I2C_DUTYCYCLE này đã được định nghĩa bên trên ở dòng 37.
phía
Dòng 66 : đị a chỉ của thiết bị I2C, ở chip master có thể không cần thiết do chip master là chip chủ động, chip muốn đượ c giao tiế p vớ i chip khác. Giống như khi bạ n muốn gọi đến một người nào đó thì bạ n cần số điện thoại của người đượ c gọi mà bạn không cần quan tâm đến số điện thoại của bạn. Dòng 67 : bật xác nhận ACK. Dòng 68 : thiết lậ p tốc độ xung clock trên chân SCL, macro đã được định nghĩa ở dòng 37.
ɥuɐɥʇ ıoʇ@thanhduongvs
50
STM32F4
Dòng 69 : chọn chế độ địa chỉ 7 bit, mạng I2C của bạn sẽ có tối đa là 128 thiết bị. Phần thiết lập cho slave tương tự như master, ở dòng 81 thiết lập địa chỉ của thiết bị slave, điều này là r ất quan tr ọng, trong giao tiếp I2C để master giao tiế p vớ i slave thì master phải biết được địa chỉ của thiết bị slave đó, tôi đã định nghĩa địa chỉ này là 0x68. Dòng 89 tôi thiết lậ p ngắt lỗi và ngắt sự kiện trên slave, mỗi khi có một hoạt động gì đó trên đườ ng truyền thì slave sẽ phân tích các sự kiện này ở hàm ngắt. K ết thúc hàm I2C_Configuration(), tôi tiế p tục giải thích phần đầu chương trình. Dòng 12 : tôi tạo mảng MasterTxBuffer[] để chứa dữ liệu mà master truyền đi, tôi cũng khở i tạo sẵn dữ liệu bên trong mảng này là 1, 2, 3. Dòng 13 : mảng chứa dữ liệu mà master sẽ nhận về. Dòng 14 : mảng chứa dữ liệu slave phát đi. Dòng 15 : mảng chứa dữ liệu mà slave nhận về. Mảng này sẽ đượ c hàm ngắt update dữ liệu.
Ở chương trình chính tôi sử dụng hàm I2C_ByteWrite() để phát dữ liệu của mảng MasterTxBuffer[] đến slave, và hàm I2C_BufferRead() để đọc dữ liệu từ slave phát đến master, dữ liệu nhận về sẽ đượ c gán vào mảng MasterRxBuffer[]. Hai hàm này đượ c tôi viết trong thư viện MY_I2C.h. Bạn vui lòng mở file stm32f4xx_it.c. Quá trình slave xử lý chủ yếu nằm trong đây.
Ở dòng 74 : mỗi khi có một sự kiện truyền nhận dữ liệu, địa chỉ,… của slave sẽ xảy ra một ngắt và sẽ đượ c tr ỏ đến hàm này. Tôi xin xét trườ ng hợ p slave truyền dữ liệu, để hoàn tất quá trình này sẽ sinh ra r ất nhiều ngắt, tôi sẽ khái quát các bướ c mà ngắt lần lượ c thực hiện: Bướ c 1 : khi phát hiện ra một sự kiện trên đườ ng truyền đầu tiên nó sẽ nhảy vào ngắt I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED l ợ i dụng việc này tôi gán Tx_Idx = 0 biến này là một biến đếm thứ tự dữ liệu gửi đi. Bướ c 2 : nếu master muốn slave gửi dữ liệu thì lần ngắt tiế p theo sẽ nhảy vào đây, ở ngắt này tôi sẽ gửi dữ liệu trong mảng SlaveTxBuffer[] đến master. Ngắt này sẽ đượ c thực hiện nhiều lần, tôi đã thực hiện phép tăng biến Tx_Idx mỗi lần xảy ra ngắt. Quá trình xảy ra cho đến khi master không muốn nhận dữ liệu nữa nó sẽ phát ra một tín hiệu k ết thúc.
Bước 3 : bướ c cuối trong quá trình truyền dữ liệu của slave, nó sẽ nhảy đến ngắt I2C_EVENT_SLAVE_STOP_DETECTED ở dòng 113. Quá trình nhận dữ liệu của slave cũng xảy ra tương tự như 3 bước trên nhưng các sự kiện có khác để phù hợ p vớ i quá trình nhận. Bạn tham khảo dòng từ 101 -> 106.
Để chắc chắn sự truyền nhận hoàn toàn thành công bạn có thể sử dụng phần mềm STMStudio kiểm tra lại các biến nhận như hình bên dướ i.
ɥuɐɥʇ ıoʇ@thanhduongvs
51
STM32F4
@04. Code trong bài vi ết: main.c 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025
#include "stm32f4xx.h" #include "MY_I2C.h" /* MASTER PB8 --- I2C1_SCL PB9 --- I2C1_SDA */ #define FAST_I2C_MODE #define Slave_Address 0x68 #define BufferSIZE 3 volatile volatile volatile volatile
uint8_t uint8_t uint8_t uint8_t
GPIO_InitTypeDef I2C_InitTypeDef NVIC_InitTypeDef
SLAVE PB10 --- I2C2_SCL PB11 --- I2C2_SDA
MasterTxBuffer[BufferSIZE] = {1 , 2 , 3}; MasterRxBuffer[BufferSIZE]; SlaveTxBuffer[BufferSIZE] = {4 , 5 , 6}; SlaveRxBuffer[BufferSIZE]; GPIO_InitStructure; I2C_InitStructure; NVIC_InitStructure;
void I2C_Configuration(void); void Delay(__IO uint32_t nCount); int main(void) {
ɥuɐɥʇ ıoʇ@thanhduongvs
52
STM32F4
026 I2C_Configuration(); 027 while (1) 028 { 029 I2C_ByteWrite(I2C1, Slave_Address, (u8*)MasterTxBuffer,3); 030 I2C_BufferRead(I2C1, Slave_Address, (u8*)MasterRxBuffer,3); 031 while(1); 032 } 033 } 034 035 void I2C_Configuration(void) 036 { 037 #ifdef FAST_I2C_MODE 038 #define I2C_SPEED 400000 039 #define I2C_DUTYCYCLE I2C_DutyCycle_16_9 040 #else /* STANDARD_I2C_MODE*/ 041 #define I2C_SPEED 100000 042 #define I2C_DUTYCYCLE I2C_DutyCycle_2 043 #endif /* FAST_I2C_MODE*/ 044 045 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 046 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); 047 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); 048 049 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; 050 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 051 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; 052 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // enable pull up resistors 053 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 054 GPIO_Init(GPIOB, &GPIO_InitStructure); 055 056 GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1); // only connect to 057 GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1); // only connect to 058 GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_I2C2); // only connect to 059 GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_I2C2); // only connect to 060 061 /************************************* Master ******************************/ 062 /* I2C De-initialize */ 063 I2C_DeInit(I2C1); 064 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; 065 I2C_InitStructure.I2C_DutyCycle = I2C_DUTYCYCLE; 066 I2C_InitStructure.I2C_OwnAddress1 = 0x01; 067 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; 068 I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED; 069 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; 070 I2C_Init(I2C1, &I2C_InitStructure); 071 /* I2C ENABLE */ 072 I2C_Cmd(I2C1, ENABLE); 073 /* Enable Interrupt */ 074 //I2C_ITConfig(I2C1, (I2C_IT_ERR ) , ENABLE); 075 //I2C_ITConfig(I2C1, (I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE); 076 /************************************* Slave ******************************/ 077 /* I2C De-initialize */ 078 I2C_DeInit(I2C2); 079 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; 080 I2C_InitStructure.I2C_DutyCycle = I2C_DUTYCYCLE; 081 I2C_InitStructure.I2C_OwnAddress1 = Slave_Address; 082 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; 083 I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED; 084 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; 085 I2C_Init(I2C2, &I2C_InitStructure);
ɥuɐɥʇ ıoʇ@thanhduongvs
53
STM32F4
086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
/* I2C ENABLE */ I2C_Cmd(I2C2, ENABLE); /* Enable Interrupt */ I2C_ITConfig(I2C2, (I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE); NVIC_InitStructure.NVIC_IRQChannel = I2C2_EV_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = I2C2_ER_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void Delay(__IO uint32_t nCount) { while(nCount--) { } } #ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
stm32f4xx_it.c 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020
#include "stm32f4xx_it.h" void NMI_Handler(void) { } void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { } } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1) { }
ɥuɐɥʇ ıoʇ@thanhduongvs
54
STM32F4
021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081
} void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) { } } void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) { } } void SVC_Handler(void) { } void DebugMon_Handler(void) { } void PendSV_Handler(void) { } void SysTick_Handler(void) { } void I2C2_ER_IRQHandler(void) { /* Check on I2C2 AF flag and clear it */ if (I2C_GetITStatus(I2C2, I2C_IT_AF)) { I2C_ClearITPendingBit(I2C2, I2C_IT_AF); } } /** * @brief This function handles I2Cx event interrupt request. * @param None * @retval None */ volatile uint32_t Event = 0; volatile uint8_t Tx_Idx; volatile uint8_t Rx_Idx; extern uint8_t SlaveTxBuffer[]; extern uint8_t SlaveRxBuffer[]; void I2C2_EV_IRQHandler(void) { /* Get Last I2C Event */ Event = I2C_GetLastEvent(I2C2); switch (Event) { /* ****************************************************************************/ /* Slave Transmitter Events */
ɥuɐɥʇ ıoʇ@thanhduongvs
55