You are on page 1of 56

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.
 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:

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 là input, chân PB0-
PB1 là output. Bạn có thể kết nối
như sơ đồ bên dướ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 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.
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 trước một số dò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 dẫn GPIO\EWARM mở file
Project.eww (IAR IDE Workspace) cho IAR hoặc vào đường dẫn GPIO\MDK-ARM mở 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 main trước vì trong một chương trình C thì
main luôn thực thi trướ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(). Hàm GPIO_Configuration() nằm từ dòng 23 -> 39.

23 void GPIO_Configuration(void)
24 {
25 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA,ENABLE);
26
27 /* Configure PB0 PB1 in output pushpull mode */
28 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
29 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
30 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
31 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
32 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
33 GPIO_Init(GPIOB, &GPIO_InitStructure);
34 /* Configure PA0 in input mode */
35 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
36 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
37 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
38 GPIO_Init(GPIOA, &GPIO_InitStructure);
39 }

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, 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? ở 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 dẫn khái
quát ở 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 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ộ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ở nộ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ộ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 thứ 3.
Hàm GPIO_Init(GPIOB, &GPIO_InitStructure); truyề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 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ạng thái không nhấn (thườ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 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, ngay lập tức kéo chân PA0 xuống mức thấp do điện trở của
dây dẫn phải nhỏ hơn điện trở treo mức cao.
Sau khi kết thúc hàm GPIO_Configuration() chương trình quay trở lại hàm main. Ở dòng 12 bạn thấy hàm
GPIO_ReadInputDataBit() có chức năng trả về 0 hoặc 1, tùy vào trạ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ạ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ạ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 GPIO_InitTypeDef GPIO_InitStructure;
04 void GPIO_Configuration(void);
05 void Delay(__IO uint32_t nCount);
06
07 int main(void)
08 {
09 GPIO_Configuration();
10 while (1)
11 {
12 if(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_0))
13 {
14 GPIO_SetBits(GPIOB ,GPIO_Pin_0 | GPIO_Pin_1 );
15 }
16 else
17 {
18 GPIO_ResetBits(GPIOB ,GPIO_Pin_0 | GPIO_Pin_1 );
19 }
20 }
21 }
22
23 void GPIO_Configuration(void)
24 {
25 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA, ENABLE);
26
27 /* Configure PB0 PB1 in output pushpull mode */
28 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
29 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
30 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
31 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
32 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
33 GPIO_Init(GPIOB, &GPIO_InitStructure);
34 /* Configure PA0 in input mode */
35 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
36 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
37 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
38 GPIO_Init(GPIOA, &GPIO_InitStructure);
39 }
40 void Delay(__IO uint32_t nCount)
41 {
42 while(nCount--)
43 {
44 }
45 }
46
47 #ifdef USE_FULL_ASSERT
48 void assert_failed(uint8_t* file, uint32_t line)
49 {
50 /* User can add his own implementation to report the file name and line number,
51 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
52
53 /* Infinite loop */
54 while (1)
55 {
56 }
57 }
58 #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 #include "stm32f4xx.h"
002
003 GPIO_InitTypeDef GPIO_InitStructure;
004 NVIC_InitTypeDef NVIC_InitStructure;
005 EXTI_InitTypeDef EXTI_InitStructure;
006

ɥuɐɥʇ ıoʇ@thanhduongvs 7
STM32F4

007 void GPIO_Configuration(void);


008 void EXTILine0_Config(void);
009 void EXTILine9_Config(void);
010 void Delay(__IO uint32_t nCount);
011 int main(void)
012 {
013 GPIO_Configuration();
014 EXTILine0_Config();
015 EXTILine9_Config();
016 while (1)
017 {
018 GPIO_ToggleBits(GPIOB,GPIO_Pin_1);
019 Delay(10000000);
020 }
021 }
022
023
024 void GPIO_Configuration(void)
025 {
026 GPIO_InitTypeDef GPIO_InitStructure;
027 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
028
029 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
030 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
031 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
032 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
033 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
034 GPIO_Init(GPIOB, &GPIO_InitStructure);
035 }
036
037 void EXTILine0_Config(void)
038 {
039 /* Enable GPIOB clock */
040 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
041 /* Enable SYSCFG clock */
042 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
043
044 /* Configure PB0 pin as input floating */
045 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
046 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
047 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
048 GPIO_Init(GPIOB, &GPIO_InitStructure);
049
050 /* Connect EXTI Line0 to PB0 pin */
051 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource0);
052
053 /* Configure EXTI Line0 */
054 EXTI_InitStructure.EXTI_Line = EXTI_Line0;
055 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
056 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
057 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
058 EXTI_Init(&EXTI_InitStructure);
059
060 /* Enable and set EXTI Line0 Interrupt to the lowest priority */
061 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
062 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
063 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
064 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
065 NVIC_Init(&NVIC_InitStructure);
066 }
067

ɥuɐɥʇ ıoʇ@thanhduongvs 8
STM32F4

068 void EXTILine9_Config(void)


069 {
070 /* Enable GPIOB clock */
071 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
072 /* Enable SYSCFG clock */
073 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
074
075 /* Configure PB5 PB6 pin as input floating */
076 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
077 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
078 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6;
079 GPIO_Init(GPIOB, &GPIO_InitStructure);
080
081 /* Connect EXTI Line5, Line6 to PB5,PB6 pin */
082 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource5);
083 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource6);
084
085 /* Configure EXTI Line5, Line6 */
086 EXTI_InitStructure.EXTI_Line = EXTI_Line5 | EXTI_Line6;
087 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
088 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
089 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
090 EXTI_Init(&EXTI_InitStructure);
091
092 /* Enable and set EXTI Line5, Line6 Interrupt to the lowest priority */
093 NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
094 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
095 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
096 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
097 NVIC_Init(&NVIC_InitStructure);
098 }
099
100
101
102 void Delay(__IO uint32_t nCount)
103 {
104 while(nCount--)
105 {
106 }
107 }
108
109
110 #ifdef USE_FULL_ASSERT
111 void assert_failed(uint8_t* file, uint32_t line)
112 {
113 /* User can add his own implementation to report the file name and line number,
114 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
115
116 while (1)
117 {}
118 }
119 #endif

stm32f4xx_it.c
01 #include "stm32f4xx_it.h"
02
03 void NMI_Handler(void)
04 {
05 }

ɥuɐɥʇ ıoʇ@thanhduongvs 9
STM32F4

06
07 void HardFault_Handler(void)
08 {
09 /* Go to infinite loop when Hard Fault exception occurs */
10 while (1)
11 {}
12 }
13
14 void MemManage_Handler(void)
15 {
16 /* Go to infinite loop when Memory Manage exception occurs */
17 while (1)
18 {}
19 }
20
21 void BusFault_Handler(void)
22 {
23 /* Go to infinite loop when Bus Fault exception occurs */
24 while (1)
25 {}
26 }
27
28 void UsageFault_Handler(void)
29 {
30 /* Go to infinite loop when Usage Fault exception occurs */
31 while (1)
32 {}
33 }
34
35 void DebugMon_Handler(void)
36 {}
37
38 void SVC_Handler(void)
39 {}
40
41 void PendSV_Handler(void)
42 {}
43
44 void SysTick_Handler(void)
45 {}
46
47 void EXTI0_IRQHandler(void)
48 {
49 if(EXTI_GetITStatus(EXTI_Line0) != RESET)
50 {
51 GPIO_ToggleBits(GPIOB,GPIO_Pin_2);
52 EXTI->PR = EXTI_Line0;
53 }
54 }
55
56 void EXTI9_5_IRQHandler(void)
57 {
58 if(EXTI_GetITStatus(EXTI_Line5) != RESET)
59 {
60 GPIO_ToggleBits(GPIOB,GPIO_Pin_3);
61 EXTI->PR = EXTI_Line5;
62 }
63 if(EXTI_GetITStatus(EXTI_Line6) != RESET)
64 {
65 GPIO_ToggleBits(GPIOB,GPIO_Pin_4);
66 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 void SysTick_Handler(void)
52 {
53 static uint32_t time=0;
54 if(++time>1000)
55 {
56 GPIO_ToggleBits(GPIOB ,GPIO_Pin_1);
57 time = 0;
58 }
59 }

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 ở file 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 #include "stm32f4xx.h"
02
03 GPIO_InitTypeDef GPIO_InitStructure;
04 void GPIO_Configuration(void);
05 void Delay(__IO uint32_t nCount);
06
07 int main(void)
08 {
09 GPIO_Configuration();
10 SysTick_Config(SystemCoreClock / 1000);
11 while (1)
12 {
13 GPIO_ToggleBits(GPIOB ,GPIO_Pin_0);
14 Delay(50000000);
15 }
16 }

ɥuɐɥʇ ıoʇ@thanhduongvs 12
STM32F4

17
18 void GPIO_Configuration(void)
19 {
20 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA, ENABLE);
21
22 /* Configure PB0 PB1 in output pushpull mode */
23 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
24 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
25 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
26 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
27 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
28 GPIO_Init(GPIOB, &GPIO_InitStructure);
29 /* Configure PA0 in input mode */
30 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
31 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
32 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
33 GPIO_Init(GPIOA, &GPIO_InitStructure);
34 }
35 void Delay(__IO uint32_t nCount)
36 {
37 while(nCount--)
38 {
39 }
40 }
41
42 #ifdef USE_FULL_ASSERT
43 void assert_failed(uint8_t* file, uint32_t line)
44 {
45 /* User can add his own implementation to report the file name and line number,
46 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
47
48 /* Infinite loop */
49 while (1)
50 {
51 }
52 }
53 #endif
54
55

stm32f4xx_it.c
01 #include "stm32f4xx_it.h"
02
03 void NMI_Handler(void)
04 {
05 }
06
07 void HardFault_Handler(void)
08 {
09 /* Go to infinite loop when Hard Fault exception occurs */
10 while (1)
11 {
12 }
13 }
14
15 void MemManage_Handler(void)
16 {
17 /* Go to infinite loop when Memory Manage exception occurs */
18 while (1)

ɥuɐɥʇ ıoʇ@thanhduongvs 13
STM32F4

19 {
20 }
21 }
22
23 void BusFault_Handler(void)
24 {
25 /* Go to infinite loop when Bus Fault exception occurs */
26 while (1)
27 {
28 }
29 }
30
31 void UsageFault_Handler(void)
32 {
33 /* Go to infinite loop when Usage Fault exception occurs */
34 while (1)
35 {
36 }
37 }
38
39 void SVC_Handler(void)
40 {
41 }
42
43 void DebugMon_Handler(void)
44 {
45 }
46
47 void PendSV_Handler(void)
48 {
49 }
50
51 void SysTick_Handler(void)
52 {
53 static uint32_t time=0;
54 if(++time>1000)
55 {
56 GPIO_ToggleBits(GPIOB ,GPIO_Pin_1);
57 time = 0;
58 }
59 }

ɥ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 #include "stm32f4xx.h"
02
03 GPIO_InitTypeDef GPIO_InitStructure;
04 NVIC_InitTypeDef NVIC_InitStructure;
05 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
06

ɥ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 /* Time base configuration */


68 TIM_TimeBaseStructure.TIM_Prescaler = 0;
69 TIM_TimeBaseStructure.TIM_Period = 0xFFFFFFFF;
70 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
71 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
72 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
73 TIM_Cmd(TIM2, ENABLE);
74 }
75
76
77 void Delay(__IO uint32_t nCount)
78 {
79 while(nCount--)
80 {
81 }
82 }
83
84
85 #ifdef USE_FULL_ASSERT
86 void assert_failed(uint8_t* file, uint32_t line)
87 {
88 /* User can add his own implementation to report the file name and line number,
89 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
90
91 while (1)
92 {}
93 }
94 #endif
95

stm32f4xx_it.c
01 #include "stm32f4xx_it.h"
02
03 void NMI_Handler(void)
04 {
05 }
06
07 void HardFault_Handler(void)
08 {
09 /* Go to infinite loop when Hard Fault exception occurs */
10 while (1)
11 {}
12 }
13
14 void MemManage_Handler(void)
15 {
16 /* Go to infinite loop when Memory Manage exception occurs */
17 while (1)
18 {}
19 }
20
21 void BusFault_Handler(void)
22 {
23 /* Go to infinite loop when Bus Fault exception occurs */
24 while (1)
25 {}
26 }
27
28 void UsageFault_Handler(void)

ɥuɐɥʇ ıoʇ@thanhduongvs 18
STM32F4

29 {
30 /* Go to infinite loop when Usage Fault exception occurs */
31 while (1)
32 {}
33 }
34
35 void DebugMon_Handler(void)
36 {}
37
38 void SVC_Handler(void)
39 {}
40
41 void PendSV_Handler(void)
42 {}
43
44 void SysTick_Handler(void)
45 {}
46
47
48 void TIM4_IRQHandler(void)
49 {
50 static uint32_t time=0;
51 if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
52 {
53 if(++time>1000)
54 {
55 GPIO_ToggleBits(GPIOB,GPIO_Pin_1);
56 time = 0;
57 }
58 TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
59 }
60 }

ɥ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 output 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-PWM2-
PWM3 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 #include "stm32f4xx.h"
02
03 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
04 TIM_OCInitTypeDef TIM_OCInitStructure;
05 GPIO_InitTypeDef GPIO_InitStructure;
06
07 void TIM_PWM_Configuration(void);
08
09 int main(void)
10 {
11 TIM_PWM_Configuration();
12 TIM1->CCR1 = 10 * 65535 / 100; // 10% Duty cycle
13 TIM1->CCR2 = 50 * 65535 / 100; // 50% Duty cycle
14 TIM1->CCR3 = 90 * 65535 / 100; // 90% Duty cycle
15 while (1)
16 {}
17 }
18
19 void TIM_PWM_Configuration(void)
20 {
21 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
22 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE);

ɥuɐɥʇ ıoʇ@thanhduongvs 23
STM32F4

23
24 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
25 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
26 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
27 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
28 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
29 GPIO_Init(GPIOA, &GPIO_InitStructure);
30
31 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
32 GPIO_Init(GPIOB, &GPIO_InitStructure);
33
34 GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM1);
35 GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);
36 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_TIM1);
37 GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_TIM1);
38 GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM1);
39 GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_TIM1);
40
41 /* Time base configuration */
42 TIM_TimeBaseStructure.TIM_Prescaler = 0;
43 TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 65535
44 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
45 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
46
47 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
48
49 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
50 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
51 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
52 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
53 TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
54 TIM_OCInitStructure.TIM_Pulse = 0;
55 //TIM_OCStructInit(&TIM_OCInitStructure);
56
57 TIM_OC1Init(TIM1, &TIM_OCInitStructure);
58 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
59
60 TIM_OC2Init(TIM1, &TIM_OCInitStructure);
61 TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
62
63 TIM_OC3Init(TIM1, &TIM_OCInitStructure);
64 TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
65
66 TIM_ARRPreloadConfig(TIM1, ENABLE);
67
68 /* TIM1 enable counter */
69 TIM_Cmd(TIM1, ENABLE);
70 TIM_CtrlPWMOutputs(TIM1, ENABLE);
71 }
72
73 #ifdef USE_FULL_ASSERT
74
75 /**
76 * @brief Reports the name of the source file and the source line number
77 * where the assert_param error has occurred.
78 * @param file: pointer to the source file name
79 * @param line: assert_param error line source number
80 * @retval None
81 */
82 void assert_failed(uint8_t* file, uint32_t line)
83 {

ɥuɐɥʇ ıoʇ@thanhduongvs 24
STM32F4

84 /* User can add his own implementation to report the file name and line number,
85 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
86
87 while (1)
88 {}
89 }
90 #endif
91
92

ɥ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 #include "stm32f4xx.h"
002 #include <stdio.h>
003
004 USART_InitTypeDef USART_InitStructure;
005 GPIO_InitTypeDef GPIO_InitStructure;
006
007 void USART_Configuration(unsigned int BaudRate);
008 void SendUSART(USART_TypeDef* USARTx,uint16_t ch);
009 int GetUSART(USART_TypeDef* USARTx);
010 /* Private function prototypes -----------------------------------------------*/
011 #ifdef __GNUC__
012 /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
013 set to 'Yes') calls __io_putchar() */
014 #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
015 #else
016 #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
017 #define GETCHAR_PROTOTYPE int fgetc(FILE *f)
018 #endif /* __GNUC__ */
019
020 void Delay(__IO uint32_t nCount)
021 {
022 while(nCount--)
023 {
024 }
025 }
026
027 int main(void)
028 {
029 USART_Configuration(38400);
030 printf(" Test\r");
031 while (1)
032 {
033 float a;
034 scanf("%f",&a);
035 printf("%3.5f\r",a);
036 }
037 }
038
039 void USART_Configuration(unsigned int BaudRate)
040 {
041 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
042 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
043
044 /* Configure USART Tx as alternate function */
045 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
046 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
047 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
048 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
049 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
050 GPIO_Init(GPIOB, &GPIO_InitStructure);

ɥuɐɥʇ ıoʇ@thanhduongvs 38
STM32F4

051
052 /* Configure USART Rx as alternate function */
053 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
054 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
055 GPIO_Init(GPIOB, &GPIO_InitStructure);
056
057 USART_InitStructure.USART_BaudRate = BaudRate;
058 USART_InitStructure.USART_WordLength = USART_WordLength_8b;
059 USART_InitStructure.USART_StopBits = USART_StopBits_1;
060 USART_InitStructure.USART_Parity = USART_Parity_No;
061 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
062 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
063 USART_Init(USART1, &USART_InitStructure);
064
065 GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_USART1);
066 GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_USART1);
067
068 USART_Cmd(USART1, ENABLE);
069 }
070
071 void SendUSART(USART_TypeDef* USARTx,uint16_t ch)
072 {
073 USART_SendData(USARTx, (uint8_t) ch);
074 /* Loop until the end of transmission */
075 while (USART_GetFlagStatus(USARTx, USART_IT_TXE) == RESET)
076 {}
077 }
078
079 int GetUSART(USART_TypeDef* USARTx)
080 {
081 while (USART_GetFlagStatus(USARTx, USART_IT_RXNE) == RESET)
082 {}
083 return USART_ReceiveData(USARTx);
084 }
085
086 /**
087 * @brief Retargets the C library printf function to the USART.
088 * @param None
089 * @retval None
090 */
091 GETCHAR_PROTOTYPE
092 {
093 return GetUSART(USART1);
094 }
095
096 PUTCHAR_PROTOTYPE
097 {
098 /* Place your implementation of fputc here */
099 /* e.g. write a character to the USART */
100 USART_SendData(USART1, (uint8_t) ch);
101
102 /* Loop until the end of transmission */
103 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
104 {}
105
106 return ch;
107 }
108
109 #ifdef USE_FULL_ASSERT
110
111 /**

ɥuɐɥʇ ıoʇ@thanhduongvs 39
STM32F4

112 * @brief Reports the name of the source file and the source line number
113 * where the assert_param error has occurred.
114 * @param file: pointer to the source file name
115 * @param line: assert_param error line source number
116 * @retval None
117 */
118 void assert_failed(uint8_t* file, uint32_t line)
119 {
120 /* User can add his own implementation to report the file name and line number,
121 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
122
123 /* Infinite loop */
124 while (1)
125 {
126 }
127 }
128 #endif
129

ɥ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 #include "stm32f4xx.h"
002
003 /* MASTER SLAVE
004 PA4 --- CONTROL_SS PB12 --- SPI2_SS
005 PA5 --- SPI1_SCK PB13 --- SPI2_SCK
006 PA6 --- SPI1_MISO PB14 --- SPI2_MISO
007 PA7 --- SPI1_MOSI PB15 --- SPI2_MOSI
008 */
009 #define SS_DIS GPIO_ResetBits(GPIOA, GPIO_Pin_4)
010 #define SS_EN GPIO_SetBits(GPIOA, GPIO_Pin_4)
011
012 volatile uint8_t SPI_data_send,SPI_data_get;
013 GPIO_InitTypeDef GPIO_InitStructure;
014 SPI_InitTypeDef SPI_InitStructure;
015 NVIC_InitTypeDef NVIC_InitStructure;
016
017 void SPI_Configuration(void);
018 void Delay(__IO uint32_t nCount);
019
020 int main(void)
021 {
022 SPI_Configuration();
023 while (1)
024 {

ɥ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 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;


086 SPI_InitStructure.SPI_CRCPolynomial = 7;
087 SPI_Init(SPI2, &SPI_InitStructure);
088
089 NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
090 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
091 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
092 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
093 NVIC_Init(&NVIC_InitStructure);
094
095 SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);
096
097 /* Enable SPI_SLAVE */
098 SPI_Cmd(SPI2, ENABLE);
099 /* Enable SPI_MASTER */
100 SPI_Cmd(SPI1, ENABLE);
101 }
102
103 void Delay(__IO uint32_t nCount)
104 {
105 while(nCount--)
106 {
107 }
108 }
109
110 #ifdef USE_FULL_ASSERT
111 void assert_failed(uint8_t* file, uint32_t line)
112 {
113 /* User can add his own implementation to report the file name and line number,
114 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
115
116 /* Infinite loop */
117 while (1)
118 {
119 }
120 }
121 #endif

stm32f4xx_it.c
01 #include "stm32f4xx_it.h"
02 #include "stm32f4xx.h"
03
04 extern uint8_t SPI_data_get;
05
06 void NMI_Handler(void)
07 {
08 }
09
10 void HardFault_Handler(void)
11 {
12 /* Go to infinite loop when Hard Fault exception occurs */
13 while (1)
14 {
15 }
16 }
17
18 void MemManage_Handler(void)
19 {
20 /* Go to infinite loop when Memory Manage exception occurs */

ɥuɐɥʇ ıoʇ@thanhduongvs 47
STM32F4

21 while (1)
22 {
23 }
24 }
25
26 void BusFault_Handler(void)
27 {
28 /* Go to infinite loop when Bus Fault exception occurs */
29 while (1)
30 {
31 }
32 }
33
34 void UsageFault_Handler(void)
35 {
36 /* Go to infinite loop when Usage Fault exception occurs */
37 while (1)
38 {
39 }
40 }
41
42 void SVC_Handler(void)
43 {
44 }
45
46 void DebugMon_Handler(void)
47 {
48 }
49
50 void PendSV_Handler(void)
51 {
52 }
53
54 void SysTick_Handler(void)
55 {
56
57 }
58
59 void SPI2_IRQHandler(void)
60 {
61 if (SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_RXNE) != RESET)
62 {
63 SPI_data_get = SPI_I2S_ReceiveData(SPI2);
64 //SPI_I2S_ClearFlag(SPI2, SPI_I2S_IT_RXNE);
65 }
66 }

ɥ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 phía
bên trên ở dòng 37.
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 #include "stm32f4xx.h"
002 #include "MY_I2C.h"
003
004 /* MASTER SLAVE
005 PB8 --- I2C1_SCL PB10 --- I2C2_SCL
006 PB9 --- I2C1_SDA PB11 --- I2C2_SDA
007 */
008 #define FAST_I2C_MODE
009 #define Slave_Address 0x68
010 #define BufferSIZE 3
011
012 volatile uint8_t MasterTxBuffer[BufferSIZE] = {1 , 2 , 3};
013 volatile uint8_t MasterRxBuffer[BufferSIZE];
014 volatile uint8_t SlaveTxBuffer[BufferSIZE] = {4 , 5 , 6};
015 volatile uint8_t SlaveRxBuffer[BufferSIZE];
016
017 GPIO_InitTypeDef GPIO_InitStructure;
018 I2C_InitTypeDef I2C_InitStructure;
019 NVIC_InitTypeDef NVIC_InitStructure;
020
021 void I2C_Configuration(void);
022 void Delay(__IO uint32_t nCount);
023
024 int main(void)
025 {

ɥ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 /* I2C ENABLE */


087 I2C_Cmd(I2C2, ENABLE);
088 /* Enable Interrupt */
089 I2C_ITConfig(I2C2, (I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF) , ENABLE);
090
091 NVIC_InitStructure.NVIC_IRQChannel = I2C2_EV_IRQn;
092 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
093 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
094 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
095 NVIC_Init(&NVIC_InitStructure);
096
097 NVIC_InitStructure.NVIC_IRQChannel = I2C2_ER_IRQn;
098 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
099 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
100 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
101 NVIC_Init(&NVIC_InitStructure);
102 }
103
104 void Delay(__IO uint32_t nCount)
105 {
106 while(nCount--)
107 {
108 }
109 }
110
111 #ifdef USE_FULL_ASSERT
112 void assert_failed(uint8_t* file, uint32_t line)
113 {
114 /* User can add his own implementation to report the file name and line number,
115 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
116
117 /* Infinite loop */
118 while (1)
119 {
120 }
121 }
122 #endif

stm32f4xx_it.c
001 #include "stm32f4xx_it.h"
002
003 void NMI_Handler(void)
004 {
005 }
006
007 void HardFault_Handler(void)
008 {
009 /* Go to infinite loop when Hard Fault exception occurs */
010 while (1)
011 {
012 }
013 }
014
015 void MemManage_Handler(void)
016 {
017 /* Go to infinite loop when Memory Manage exception occurs */
018 while (1)
019 {
020 }

ɥuɐɥʇ ıoʇ@thanhduongvs 54
STM32F4

021 }
022
023 void BusFault_Handler(void)
024 {
025 /* Go to infinite loop when Bus Fault exception occurs */
026 while (1)
027 {
028 }
029 }
030
031 void UsageFault_Handler(void)
032 {
033 /* Go to infinite loop when Usage Fault exception occurs */
034 while (1)
035 {
036 }
037 }
038
039 void SVC_Handler(void)
040 {
041 }
042
043 void DebugMon_Handler(void)
044 {
045 }
046
047 void PendSV_Handler(void)
048 {
049 }
050
051 void SysTick_Handler(void)
052 {
053 }
054
055 void I2C2_ER_IRQHandler(void)
056 {
057 /* Check on I2C2 AF flag and clear it */
058 if (I2C_GetITStatus(I2C2, I2C_IT_AF))
059 {
060 I2C_ClearITPendingBit(I2C2, I2C_IT_AF);
061 }
062 }
063
064 /**
065 * @brief This function handles I2Cx event interrupt request.
066 * @param None
067 * @retval None
068 */
069 volatile uint32_t Event = 0;
070 volatile uint8_t Tx_Idx;
071 volatile uint8_t Rx_Idx;
072 extern uint8_t SlaveTxBuffer[];
073 extern uint8_t SlaveRxBuffer[];
074 void I2C2_EV_IRQHandler(void)
075 {
076 /* Get Last I2C Event */
077 Event = I2C_GetLastEvent(I2C2);
078 switch (Event)
079 {
080 /* ****************************************************************************/
081 /* Slave Transmitter Events */

ɥuɐɥʇ ıoʇ@thanhduongvs 55
STM32F4

082 /* */
083 /* ****************************************************************************/
084
085 /* Check on EV1 */
086 case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:
087 Tx_Idx = 0;
088 I2C_ITConfig(I2C2, I2C_IT_BUF , ENABLE);
089 break;
090 /* Check on EV3 */
091 case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:
092 case I2C_EVENT_SLAVE_BYTE_TRANSMITTED:
093 I2C_SendData(I2C2, SlaveTxBuffer[Tx_Idx++]);
094 break;
095
096 /* ****************************************************************************/
097 /* Slave Receiver Events */
098 /* */
099 /* ****************************************************************************/
100
101 /* check on EV1*/
102 case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
103 Rx_Idx = 0;
104 break;
105
106 /* Check on EV2*/
107 case I2C_EVENT_SLAVE_BYTE_RECEIVED:
108 case (I2C_EVENT_SLAVE_BYTE_RECEIVED | I2C_SR1_BTF):
109 SlaveRxBuffer[Rx_Idx++] = I2C_ReceiveData(I2C2);
110 break;
111
112 /* Check on EV4 */
113 case I2C_EVENT_SLAVE_STOP_DETECTED:
114 I2C_GetFlagStatus(I2C2, I2C_FLAG_STOPF);
115 I2C_Cmd(I2C2, ENABLE);
116 break;
117
118 default:
119 break;
120 }
121 }

ɥuɐɥʇ ıoʇ@thanhduongvs 56

You might also like