Yêu cầu: Nắm vững phương pháp quét led bằng timer mà mình đã hướng dẫn trong suốt tutorial
Trong toàn bộ tutorial, chúng ta đã quá quen với kĩ thuật quét led matrix sử dụng ngắt timer, nhược điểm của phương pháp này là làm hao tốn nhiều thời gian xử lí của chip vì phải thường xuyên nhảy vào chương trình ngắt, bản thân chương trình ngắt lại thực hiện khá lâu do phải truyền data dưới dạng nối tiếp vào các thanh ghi dịch
DMA là 1 cơ chế bơm dữ liệu tự động, chúng ta sẽ giảm tải cho CPU bằng cách nhờ DMA truyền dữ liệu ra cho các thanh ghi dịch
DMA chỉ có khả năng bơm dữ liệu chứ không tính toán dữ liệu hộ CPU được, do vậy cần có 1 bộ nhớ đệm do CPU chuẩn bị sẵn từ trước để DMA thực hiện truyền, do vậy nhược điểm khi sử dụng DMA là sẽ tốn RAM
Quay trở lại hàm ngắt quét led mà mình hay xài ở các bài trước
Trong hàm ngắt quét led này, đoạn code mà mình tô vuông chính là đoạn truyền dữ liệu ra led và là nơi mà CPU mất thời gian xử lí nhất. Khi bảng led nối tiếp càng nhiều tấm thì đoạn code này thực thi càng lâu hơn nữa, trong khi các dòng code còn lại thì hầu như không đáng kể. Do vậy chúng ta sẽ “nhờ” DMA thực hiện giúp đoạn code tô vuông đó. Sau khi DMA thực hiện xong việc truyền data thì 1 hàm callback sẽ được gọi để chúng ta thực thi các dòng code còn lại bên dưới
Tóm lại, phương pháp thống thường thì như này
Còn dùng DMA sẽ như này
Nhờ có DMA chúng ta sẽ tiết kiệm được cho hàm main rất nhiều thời gian
Thiết kế bộ đệm cho DMA
Do mình thiết kế các chân R1G1B1 R2G2B2 đề nối vào PORTA nên DMA sẽ tự lấy dự liệu trong bộ đệm để ghi vào PORTA của ic STM32F103C8T6
Do mỗi lần truyền một bit data thì phải có 1 xung clock (theo nguyên tắc của thanh ghi dịch) nên mình cho chân clk của module led chung port với các chân dữ liệu RGB. Sau khi truyền xong 1 bit dữ liệu ra các chân RGB thì mình tiếp tục truyền dữ liệu đó 1 lần nữa nhưng ở vị trí của chân CLK sẽ đảo để tạo ra xung dịch. Điều này dẫn tới việc phải gấp đôi bộ nhớ ram lên
Lập trình
Tiếp tục project ở bài 16, tuy nhiên chân kết nối sẽ khác 1 chút ở chân CLK, mình sẽ đưa chân này về PORTA.0
STM32F103C8 | HUB75D |
A2 | R1 |
A3 | G1 |
A4 | B1 |
A5 | R2 |
A6 | G2 |
A7 | B2 |
A0 | CLK |
B1 | A |
B2 | D |
B10 | C |
B11 | OE |
B12 | LAT |
B14 | B |
Đối với bạn nào dùng mạch Clock P5 của mình thì hãy gỡ con biến trở ra và hàn câu dây chân CLK vào chân A0 nhé
Khởi tạo CUBE MX
Mình sẽ sử dụng DMA TIM4_UP của timer4 để truyền data tự động ra PORTA
Chuyển sang tab cấu hình DMA, add TIM4_UP vào và cài đặt như hình nhé
Chúng ta cần 1 ngắt timer để quét led nữa, nhiệm vụ của timer này chỉ đơn giản là kích hoạt cho DMA bắt đầu truyền. Mình sẽ dùng timer3, tần số ngắt này sẽ quyết định tần số làm tươi của màn hình LED, như cài đặt của mình ở dưới thì nó sẽ quét khoảng 400Hz (khá cao, thực chất chỉ cần 60hz là đủ nhìn rồi )
Đừng quên chuyển sang tab NIVC để kích hoạt cho phép ngắt
Bây giờ tiếp tục khởi tạo timer2 chanel 4 để tạo 1 kênh PWM vào chân OE, hệ số chia để 1 hoặc 0 tùy các bạn, cứ tạo pwm càng mịn càng tốt, còn Couter Period không cần quan tâm vì ta sẽ chỉnh nó trong code
Đừng quên cấu hình các chân GPIO
Tạo code bằng phần mềm lập trình mà bạn thích, như ở đây mình xài KeilC V5
Do phải gấp đôi bộ đệm GPIO nên mình sẽ chỉ tạo pwm 4bit, bộ đệm GPIO cho tấm led P5 64x32px và bộ đệm RGB sẽ như này
1 2 |
uint8_t Buffer_GPIO[SCAN_TYPE*MAX_BIT*64*2]; uint8_t Buffer_RGB[3*32*64] |
Nếu dùng 8bit pwm thì không đủ RAM nên project này mình sẽ quét với 6bit pwm
1 2 |
#define SCAN_TYPE 16 #define MAX_BIT 6 |
Hàm chuyển đổi bộ đệm RGB sang bộ đệm GPIO sẽ được viết lại 1 chút do phải nhồi các byte clk vào giữa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void display(void) { for(int hang=0;hang<SCAN_TYPE;hang++) { uint8_t dat; for(int ts=0;ts<MAX_BIT;ts++) { for(int i=0;i<64;i++) { dat = 0; if((Buffer_RGB[1*arrD1 + (16+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<G2);} //ghi bit 7 G2 if((Buffer_RGB[1*arrD1 + (0+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<G1);} //ghi bit 0 G1 if((Buffer_RGB[0*arrD1 + (0+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<R1);} //ghi bit 1 R1 if((Buffer_RGB[2*arrD1 + (0+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<B1);} //ghi bit 4 B1 if((Buffer_RGB[0*arrD1 + (16+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<R2);} //ghi bit 6 R2 if((Buffer_RGB[2*arrD1 + (16+hang)*arrD2 + i] & (1<<ts)) != 0 ){dat|=(0x01<<B2);} //ghi bit 9 B2 Buffer_GPIO[hang*MAX_BIT*64*2 + ts*64*2 + (i*2)]=dat; Buffer_GPIO[hang*MAX_BIT*64*2 + ts*64*2 + (i*2)+1]=dat|0x01; } } } } |
Chương trình ngắt timer quét led chỉ dơn giản là kích hoạt cho DMA bắt đầu truyền
1 2 3 4 5 |
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM3) HAL_DMA_Start_IT(&hdma_tim4_up, (uint32_t)(Buffer_GPIO) + (scan_data[scan_line]*MAX_BIT*(64*2) + bit_pos*(64*2)), (uint32_t)&GPIOA->ODR, 128); //tien hanh truyen du lieu } |
Và chương trình callback sau khi DMA truyền xong
1 2 3 4 5 6 7 8 9 10 |
void DMA_ngat(DMA_HandleTypeDef * _hdma) //ngat truyen xong data { __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_4,0); scan16S(scan_data[scan_line]); Control_P->BSRR=(1<<LAT); // chot data Control_P->BSRR=(1<<(LAT+16)); __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_4,scan_PWM_duty[bit_pos]); bit_pos++; if(bit_pos==MAX_BIT){bit_pos=0;scan_line++;if(scan_line==SCAN_TYPE)scan_line=0;} } |
Trong hàm main chúng ta sẽ khởi động timer, phát pwm, cấu hình DMA …
1 2 3 4 5 6 |
HAL_DMA_RegisterCallback(&hdma_tim4_up,HAL_DMA_XFER_CPLT_CB_ID,&DMA_ngat); //connect callback __HAL_TIM_ENABLE_DMA(&htim4, TIM_DMA_UPDATE); //bat DMA cho timer4 __HAL_TIM_ENABLE(&htim4); //kich hoat timer HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); //kich hoat pwm chan OE HAL_TIM_Base_Start_IT(&htim3); //kich hoat timer3 quet led |
Hàm HAL_DMA_RegisterCallback dùng để đăng kí hàm callback sẽ được gọi khi DMA truyền xong, các hàm còn lại dùng để kích hoạt DMA, timer, pwm bắt đầu hoạt động
Bây giờ dùng ứng dụng Image To Data Convert để tạo mã hex cho buffer RGB để hiển thị thử 1 hình ảnh như này nhé
Phần mềm này mình đã share ở các bài trước rồi. Sau khi lấy mã mình sẽ dán thẳng nó vào chỗ khởi tạo bộ đệm RGB luôn
Đừng gọi hàm display() để cập nhật dữ liệu từ bộ đệm RGB sang GPIO
Download
Tải source và file cấu hình cubemx tại đây
Tóm lại với mỗi phương pháp đều có ưu nhược điểm riêng, bản thân cách quét dùng dma mình đã chia sẻ này cũng chưa phải là tối ưu các bạn tự nghiên cứu các thuật toán hay hơn và chia sẻ cho mọi người nhé
Tag: PWM HUB 75 SCAN LED MATIX DMA SCAN LED TIMER TRIGGER DMA STM32 LED MATRIX
I think using DMA can save a lot of CPU resources.
trên chân PB2 của stm32 bluepill nó nối tiếp với 1 con trở 100k thì để chạy được code này em có cần thông trở 100k đó không ạ
b có thể chuyển chân B2 sang chân dùng khác cũng được mà