Chào các bạn, nay mình có 1 dự án nho nhỏ dùng tới dây LED sử dụng ic driver UCS1903 nên tiện làm 1 bài hướng dẫn qua về cách giao tiếp và điều khiển led dây này luôn, (dây led UCS1903 với WS2812 đều có nguyên tắc điều khiển khá giống nhau nên các bạn có thể tham khảo luôn nhé) đây là cuộn led dây của mình:
Do của mình nó sử dụng điện áp 12V nên có tận 3 con led nối tiếp nhau (sáng cùng nhau) cho 1 ic UCS1903, nếu loại có ghi 5V thì sẽ chỉ có 1 LED cho 1 ic
Lúc mới mua thì ở đầu dây led có sẵn 1 con vi điều khiển nhỏ để tạo hiệu ứng demo, nên chúng ta có thể cắt bỏ nó đi để đưa xung điều khiển vào ic 1903
Nguyên tắc điều khiển
Để điều khiển thì chúng ta chỉ cần đưa tín hiệu điều khiển vào chân DI của con ic 1903 đầu tiên, dữ liệu cứ thế nối tiếp đến các con IC sau.
Mỗi ic điều khiển 1 LED cần 24bit dữ liệu tương ứng với 8bit màu đỏ (R) 8bit màu xanh lá (G) và 8bit màu xanh dương (B), tổng là 24bit cho 1 LED, chúng ta sẽ sắp xếp các bit này nối tiếp nhau theo thứ tự bit MSB (bit cao nhất) được gửi đi trước, nhìn hình cho dễ hiểu nhé !
Theo datasheet mình tham khảo thì gửi R -> G -> B nhưng mình test thực tế thì thứ tự đúng của nó lại là B->R->G
Bit 1 được định nghĩa như sau:
Bit 0 được định nghĩa như sau:
Theo data sheet thì sai số cho phép là +- 150ns . Tuy nhiện m test thực tế thì sai số cho phép khá là lớn 😛
Sau khi truyền đủ ic 1903 nhận đủ 24bit, nó sẽ tạm lưu 24 bit nó nhận được vào bộ nhớ của nó chứ nó không xuất tín hiệu PWM ra LED ngay đâu. Sau đó nó sẽ không nhận xung điều khiển nữa mà chuyển các xung này sang chân DO (chân này được nối với DI của con ic1903 thứ 2), lúc này tín hiệu của bạn được “bắc cầu” để cho bạn điều khiển con ic thứ 2, tương tự khi con ic thứ 2 đã “ăn no” ( tau đã nhận đủ 24 bit) nó lại lưu lại rồi lại “bắc cầu” xung điều khiển của bạn sang cho con thứ 3 …v…v… cứ như thế cho đến khi bạn kéo chân tín hiệu xuống mức 0 trong khoảng >24us thì tất cả các ic1903 đồng loạt RESET
Tín hiệu RESET được định nghĩa như sau:
Tín hiệu RESET sẽ làm cho IC1903 xuất 24bit data mã nó đã nhận được lúc nãy ra để thay đổi độ sáng của LED, đồng thời các ic đều trở về trạng thái sẵn sàng nhận dữ liệu (nhả ra rồi thì giờ nó lại hứng tiếp)
Vậy là kết thúc 1 chu kì điều khiển, do tổng thời gian gửi 1 bit đều là 2.5us nên ta có tần số gửi dữ liệu tối đa ta có thể là 1/2.5us=0.4Mhz=400Khz
À quên, con ic1903 còn có 1 chân SET, nếu để trống chân này thì mặc định nó sẽ hoạt động tối đa ở 400Khz, còn nêu treo chân SET lên VCC thì nó sẽ hoạt động ở chế độ HIGH SPEED lên tới 800Khz ( thời gian gửi bit1 bit0 cũng sẽ giảm đi chỉ còn 1 nửa thôi nhé)
Lập trình điều khiển dây LED với stm32c8t6
Nếu bạn chỉ cần tạo ra các hiệu ứng led đơn giản theo kiểu chạy chạy thì có thể mắc nối tiếp vô cực dây led phía sau. Nhưng để làm mới toàn bộ dây LED đồng thời thì ở mode LOW SPEED(400Khz) chúng ta sẽ chỉ điều khiển được 512 LED để không bị nhấp nháy và 1024 LED ở HIGH SPEED (800Khz)
Phương pháp điều khiển đơn giản nhất là tạo 1 hàm delay 0.5us và 1 hàm delay 2us bằng các lệnh __NOP _NOP rồi cứ thế mà bắn data ra thôi. Tuy nhiên nhiên hay nhớ rằng 0.5us là 1 con số cực kì nhỏ cho vi điều khiển, chỉ 1 hoạt động ra vào ngắt cơ bản cũng có thể phá hỏng timing của bạn, dẫn đến data gửi đi sai bét hết. Để không bị như vậy bạn phải disable toàn bộ ngắt trong con chip của bạn trong khi truyền data ra dây led, điều này sẽ làm hạn chế khả năng làm việc của MCU. Hơn nữa giả sử số lượng led bạn cần điều khiển lên đến 512 con led. Bạn sẽ cần khoảng 30ms cho 1 lần truyền, và để đảm tốc độ làm mới 24 hình/s thì 1 giây bạn sẽ phải bỏ ra 720ms chỉ để truyền data mà không thể làm đc bất kì 1 công việc nào khác.
Do đó, mình sẽ giới thiệu 1 phương pháp điều khiển bằng cách kết hợp giao thức SPI và cơ chế DMA của dòng ARM
Chắc chắn còn rất nhiều các khác hay hơn nên nếu bạn nào biết hãy chia sẽ nhé Kết 😛
Ý Tưởng:
1 bộ đệm khá lớn sẽ đc bỏ ra để lưu data chúng ta đã tính toán và xắp xếp sẵn. DMA là 1 cơ chế tự động bơm dữ liệu, nó sẽ liên tục gửi data ra LED mà CPU không cần phải tốn tí công sức nào để làm 😛
Công việc chỉ đơn là set data vào bộ đệm này để thay đổi dữ liệu cần hiển thị ra LED.
Ưu điểm: CPU quá nhàn rỗi, làm công việc khác thả ga mà chả bận tâm gì
Nhược điểm: Tốn RAM
Nhìn lại timing của data:
– bit 1 gồm 2us cao – 0.5us thấp
– bit 0 gồm 2us cao – 0.5us thấp
Cả 2 đều có tổng thời gian là 2.5us
Chúng ta cấu sẽ cấu hình SPI với clk xấp xỉ 0.5us cho 1 bit, nghĩa là bộ SPI sẽ gửi đi 1 bit dữ liệu trong 0.5us, và gửi đi 4 bit dữ liệu trong 2us. Nói cách khác để gửi ra 1 bit cho UCS1903, ta sẽ cho bộ SPI gửi 5 bit và cấu trúc của 5bit này như sau:
11110 cho bit 1
10000 cho bit 0
Như vậy với thủ thuật này mình sẽ hi sinh tới 5bit RAM chỉ để truyền đi 1 bit có ích, bù lại nhận đc 1 CPU rảnh rỗi hoàn toàn. 1 sự đánh đổi có thể chấp nhận được
Bây giờ chúng ta phải “design” bộ đệm để dâng tới miệng cho anh DMA đập phá.
Do cứ 1 bit có nghĩa cần tới 5bit ram nên 1 byte có nghĩa sẽ cần tới 40bit ram = 5 byte ram. Ví dụ byte 0xFF có nghĩa sẽ như sau:
-> Do data trong ram của ta là các byte 8bit nên xắp xếp lại thành cặp 8bit nó là 5 byte trông như này:
Và đó là 8bit, 1 ic 1903 cần 24bit cho 3 thành phần màu RGB nên với mỗi 1 ic sẽ cần 15 byte, mình sẽ xắp xếp chúng liên tiếp nhau, à mà như dây led của mình thì là BRG
Hãy nhớ rằng bạn cần ít nhất 24us ở mức 0 để tạo tín hiệu RESET chốt dữ liệu nên mình để ở cuối bộ đệm tầm 20byte có giá trị là 0x00 để xuất mức 0 (20byte = 160 bit => thời gian ở mức 0 là 160×0.5us = 80us > 24us (vậy là OK)
Bộ đệm đã sẵn sàng cho DMA bắn liên tục ra cho dây LED, công việc của ta là viết các hàm để thay đổi data trọng bộ đệm đó thôi
Phần cứng
Mình sẽ sử dụng dây LED và KIT STM42F103C8T8 bluepill và 1 cục nguồn 12V. chân DI của dây led nối vào chân B5 (MOSI) của bộ SPI1
Phần mềm
Thư viện HAL CUBEMX + IDE KEILC5:
Chúng ta cấu hình cubemx như sau:
OK, giờ quay về bộ SPI1 để tính toán cấu hình clock cho bộ SPI1. Ở phần này mình sẽ tính toán 1 chút, do timing nhỏ nhất khi gửi data là 0,5us nên mình sẽ cấu hình clock của spi sao cho có chu kì gần sát với con số 0.5us nhất. Tức là tầb số khoảng 1/0.5us = 2Mhz. Bộ chia clock cho SPI chỉ có các mức chia 2,4,8,16,32,64,128,256. Nếu chọn hệ số chia 32 thì mình sẽ được 72M/32=2.25M tương đương khoảng 0.44us ( sai số này là chấp nhận được)
Vậy chúng ta sẽ cấu hình hệ số chia 32 cho bộ SPI1, các thông số còn lại giữ nguyên
Cũng trọng mục cấu hình SPI1, chuyển sang DMA Setting và kích hoạt DMA TX
Xuất mã code tùy theo công cụ mà bạn sử dụng, như mình dùng keilcV5 nên mình sẽ chọn MDK-ARM
Trước khi đi vào lập trình mình sẽ chỉnh sửa mã code mã cubeMX tạo ra 1 chút
Các ban nên comment dòng __HAL_AFIO_REMAP_SWJ_DISABLE(); ở file stm32f1xx_hal_msp.c (cùng cấp với main.c) để nạp code và debug cho thuận tiện
Trong hàm main đưa dòng MX_DMA_Init(); lên trên MX_SPI1_Init(); vì DMA phải được khởi tạo trước SPI. Đây là 1 lỗi nhỏ của cubemx ở phiên bản mình đang dùng, chắc sẽ sớm được khắc phục
Vậy là xong các bước khởi tạo cơ bản, giờ bắt đầu code thôi:
Ví dụ mình cần điều khiển 512 LED, mình sẽ tạo 1 bộ đệm gồm 512×15+20 =7700 byte
1 |
unsigned char LED[7700]; |
Hãy nhớ rằng bit0 là 10000 và bit 1 là 11110 nên để bộ đệm lúc đầu có giá trị khởi tạo là bit0 có nghĩa thì ta phải nạp bit0 10000 vào bộ đệm, mình sẽ viết 1 hàm init bộ đệm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void LED_init() { for(int led=0;led<512;led++) { int index=15*led; for(int i=0;i<3;i++) { LED[i*5+index]=0x84; LED[1+i*5+index]=0x21; LED[2+i*5+index]=0x08; LED[3+i*5+index]=0x42; LED[4+i*5+index]=0x10; } } } |
OK bộ đệm đã sẵn sàng, trước khi vào while(1) chúng ta sẽ khởi động DMA:
1 |
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t*)LED,7700); |
Tiếp tục xây dựng các hàm set led cơ bản
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
void setledB(int led,unsigned char data) { led=15*led; for(int i=0;i<5;i++)LED[led+i]=0; //clear data cu if(data&0x80){LED[led]=0xF0;} else {LED[led]=0x80;} if(data&0x40){LED[led]|=0x07;LED[led+1]=0x80;} else {LED[led]|=0x04;} if(data&0x20){LED[led+1]|=0x3C;} else {LED[led+1]|=0x20;} if(data&0x10){LED[led+1]|=0x01;LED[led+2]=0xE0;} else {LED[led+1]|=0x01;} if(data&0x08){LED[led+2]|=0x0F;} else {LED[led+2]|=0x08;} if(data&0x04){LED[led+3]=0x78;} else {LED[led+3]=0x40;} if(data&0x02){LED[led+3]|=0x03;LED[led+4]=0xC0;} else {LED[led+3]|=0x02;} if(data&0x01){LED[led+4]|=0x1E;} else {LED[led+4]|=0x10;} } void setledR(int led,unsigned char data) { led=15*led+5; for(int i=0;i<5;i++)LED[led+i]=0; //clear data cu if(data&0x80){LED[led]=0xF0;} else {LED[led]=0x80;} if(data&0x40){LED[led]|=0x07;LED[led+1]=0x80;} else {LED[led]|=0x04;} if(data&0x20){LED[led+1]|=0x3C;} else {LED[led+1]|=0x20;} if(data&0x10){LED[led+1]|=0x01;LED[led+2]=0xE0;} else {LED[led+1]|=0x01;} if(data&0x08){LED[led+2]|=0x0F;} else {LED[led+2]|=0x08;} if(data&0x04){LED[led+3]=0x78;} else {LED[led+3]=0x40;} if(data&0x02){LED[led+3]|=0x03;LED[led+4]=0xC0;} else {LED[led+3]|=0x02;} if(data&0x01){LED[led+4]|=0x1E;} else {LED[led+4]|=0x10;} } void setledG(int led,unsigned char data) { led=15*led+10; for(int i=0;i<5;i++)LED[led+i]=0; //clear data cu if(data&0x80){LED[led]=0xF0;} else {LED[led]=0x80;} if(data&0x40){LED[led]|=0x07;LED[led+1]=0x80;} else {LED[led]|=0x04;} if(data&0x20){LED[led+1]|=0x3C;} else {LED[led+1]|=0x20;} if(data&0x10){LED[led+1]|=0x01;LED[led+2]=0xE0;} else {LED[led+1]|=0x01;} if(data&0x08){LED[led+2]|=0x0F;} else {LED[led+2]|=0x08;} if(data&0x04){LED[led+3]=0x78;} else {LED[led+3]=0x40;} if(data&0x02){LED[led+3]|=0x03;LED[led+4]=0xC0;} else {LED[led+3]|=0x02;} if(data&0x01){LED[led+4]|=0x1E;} else {LED[led+4]|=0x10;} } |
Ví dụ để thay đổi giá trị màu đỏ của LED thứ 0 (con led gần MCU nhất) thành 255(sáng nhất) ta sẽ gọi
setledR(0,255)
DEMO 1 vài chương trình nháy LED cơ bản
Sáng dần tối dần
1 2 3 4 5 6 7 8 9 10 |
for(int sang=0;sang<256;sang++) { setledG(0,sang); HAL_Delay(5); } for(int sang=255;sang>0;sang--) { setledG(0,sang); HAL_Delay(5); } |
Hmm… có vẻ hiệu ứng sáng dần tắt dần chưa đẹp lắm, đó là do con ic này chưa xử lí gamma tốt ! Chúng ta sẽ tự hiệu chỉnh gamma bằng code. Tạo 1 array lữu trữ giá trị tra cứu gamma
1 2 3 4 5 6 7 8 9 10 11 |
//Bang tra cuu gamma 2.3 126 -> 255 const unsigned char gamma[]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32, 33, 35, 36, 38, 39, 41, 43, 45, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 73, 75, 77, 80, 82, 85, 87, 90, 92, 95, 98,100,103,106,109,112,115,118,121,124,127,130,133, 136,140,143,146,150,153,157,160,164,168,171,175,179,183,187,191, 194,199,203,207,211,215,219,224,228,232,237,241,246,250,255 }; |
Chỉnh sửa lại code sáng dần tối dần:
1 2 3 4 5 6 7 8 9 10 |
for(int sang=0;sang<127;sang++) { setledG(0,gamma[sang]); HAL_Delay(5); } for(int sang=126;sang>0;sang--) { setledG(0,gamma[sang]); HAL_Delay(5); } |
LED sao băng đơn giản
1 |
unsigned char sb[]={1,3,5,8,10,20,30,40,50,60,70,80,90,254}; //data 14 led sao bang |
1 2 3 4 5 6 7 8 9 10 11 12 |
for(int i=0;i<num_led+13;i++) { LED_init();//clear LED int dem=13; for(int s=0;s<14;s++) { if(i-s<0)break; if(i-s >=num_led){dem--;continue;} setledR(i-s,sb[dem--]); } HAL_Delay(30); } |
Cho e hỏi phần đẩy dữ liệu ra chân DATA e dùng ngắt TIMER 0.5us cũng đc đúng ko ạ?
cũng đc nhưng ngắt 0.5us thì hơi xa xỉ đó bạn
Anh cho em hỏi cái biến num_led dùng để làm gì ạ. Em k dịch được chỗ đó
“Nhưng để làm mới toàn bộ dây LED đồng thời thì ở mode LOW SPEED(400Khz) chúng ta sẽ chỉ điều khiển được 512 LED để không bị nhấp nháy” a cho e hỏi đoạn này với e tính ra hơn 512 led vẫn đạt 24 khung hình /s mà. a giải thích đoạn này cho e được ko ạ.cảm ơn anh
đúng rồi vẫn điều khiển được 512 led ở 24fps. ý mình là nếu cố điều khiển nhiều led hơn thì khung hình sẽ tụt thôi, nói là nhấp nháy là chưa đúng lắm
Cảm ơn tác giả .Bài viết rất hay .Mong bạn hướng dẫn thêm sử dụng úc1903 với arduino ,thân chào .
Cùng mong muốn giống như bạn !
Cảm ơn tác giả, gần như đây là bài hướng dẫn duy nhất về Led này thì phải 😀
Cho mình xin toàn bộ file code tham khảo với được không vậy. Cảm ơn bạn !