Professional Documents
Culture Documents
Toaz - Info stm32f4 PR
Toaz - Info stm32f4 PR
STMicroelectronics
ɥuɐɥʇ ıoʇ@thanhduongvs 1
STM32F4
Đố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.
ɥuɐɥʇ ıoʇ@thanhduongvs 2
STM32F4
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
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.
ɥ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
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.
ɥuɐɥʇ ıoʇ@thanhduongvs 6
STM32F4
ɥuɐɥʇ ıoʇ@thanhduongvs 7
STM32F4
ɥuɐɥʇ ıoʇ@thanhduongvs 8
STM32F4
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 }
ɥuɐɥʇ ıoʇ@thanhduongvs 11
STM32F4
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.
ɥ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
ɥ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.
ɥ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
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
ɥuɐɥʇ ıoʇ@thanhduongvs 20
STM32F4
ɥ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
ɥ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
ɥuɐɥʇ ıoʇ@thanhduongvs 26
STM32F4
ɥuɐɥʇ ıoʇ@thanhduongvs 27
STM32F4
ɥ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
002
007
009
011
013 {
014 ADC_Config();
016 {
ɥuɐɥʇ ıoʇ@thanhduongvs 30
STM32F4
017
018 }
019 }
020
021
023 {
027
032
037 DMA_InitStructure.DMA_BufferSize = 3;
049
ɥuɐɥʇ ıoʇ@thanhduongvs 31
STM32F4
052
053
059 ADC_CommonInit(&ADC_CommonInitStructure);
060
066 ADC_InitStructure.ADC_NbrOfConversion = 3;
068
073
076
079
082
084 ADC_SoftwareStartConv(ADC1);
ɥuɐɥʇ ıoʇ@thanhduongvs 32
STM32F4
085 }
086
087
089 {
090 while(nCount--)
091 {
092 }
093 }
094
095
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
103 {}
104 }
105 #endif
106
ɥuɐɥʇ ıoʇ@thanhduongvs 33
STM32F4
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
ɥ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.
ɥuɐɥʇ ıoʇ@thanhduongvs 36
STM32F4
ɥ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.
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
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".
ɥuɐɥʇ ıoʇ@thanhduongvs 42
STM32F4
ɥ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
ɥ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
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
ɥuɐɥʇ ıoʇ@thanhduongvs 49
STM32F4
ɥ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
ɥ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
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