embeded/Cortex-M3 STM
네오픽셀 ws2812b-64 작동
구차니
2026. 9. 23. 12:08
stm32f103 / bluepill
네오픽셀 ws2812b-64
+ claude
PB0 를 신호선으로 GND는 연결해주어야 한다.
white로 하면 최대 밝기가 아니어도 미친듯이 눈이 부시고 4.5V / 1.6A 먹는데
레인보우 패턴으로 바꾸니 4V / 0.15A 정도로 착하게 먹는다.

저기가 1번 LED

하나 더 켜진게 led 2번

뒤집으면 입력핀 바로 뒤에있는게 1번이다.

직접 코딩해보려다가 작동만 확인하려고 ai 써서 일단 진행
낮은 클럭으로 하다가 안되서 72MHz로 올리니 정상적으로 작동한다. 역시 SPI로 가야하나?
| /* -------------------- WS2812 (PB0, bit-bang, DWT cycle count) -------------------- */ /* Cortex-M3 DWT 사이클 카운터 활성화 (마이크로초 이하 정밀 타이밍용) */ static void DWT_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } /* 비트 1개 전송: 기준 시각(start) 대비 상대시간으로 High->Low 구간을 제어해 * 분기/함수호출 오버헤드로 인한 지터를 최소화한다. * always_inline: -O0 빌드에서도 GCC가 반드시 인라인하도록 강제 (그냥 inline은 * -O0에서 무시되어 매 비트마다 진짜 함수호출(push/pop)이 발생, 비트 사이 LOW * 구간이 수십배 길어져 스트립이 매 비트를 리셋으로 오인하는 문제가 있었음). */ __attribute__((always_inline)) static inline void ws2812_send_bit(uint8_t bit) { uint32_t start = DWT->CYCCNT; uint32_t high_cycles = bit ? WS_T1H_CYCLES : WS_T0H_CYCLES; WS2812_PORT->BSRR = WS2812_PIN; /* Data line High */ while ((DWT->CYCCNT - start) < high_cycles) { } WS2812_PORT->BSRR = (uint32_t)WS2812_PIN << 16U; /* Data line Low */ while ((DWT->CYCCNT - start) < WS_BIT_CYCLES) { } } __attribute__((always_inline)) static inline void ws2812_send_byte(uint8_t byte) { for (int8_t i = 7; i >= 0; i--) { ws2812_send_bit((byte >> i) & 0x01U); } } /* led_grb[] 버퍼 내용을 스트립으로 전송. 전송 중 인터럽트(특히 USB)로 인한 * 타이밍 흐트러짐을 막기 위해 전송 구간만 짧게(약 1.92ms/64LED) IRQ를 끈다. * * 전송 전에 예상 총 전류(mA)를 계산해서 WS_MAX_TOTAL_MA를 넘으면 전체 밝기를 * 비율대로 깎아서 보낸다. led_grb[]는 원본 그대로 두고(다음 프레임 색상 계산에 * 영향 없음), 실제 출력만 스케일한다. */ static void ws2812_show(void) { uint32_t est_ma = (uint32_t)NUM_LEDS * WS_MA_IDLE_PER_LED; for (uint32_t i = 0; i < NUM_LEDS; i++) { uint32_t sum = (uint32_t)led_grb[i][0] + led_grb[i][1] + led_grb[i][2]; est_ma += (sum * WS_MA_PER_CH_FULL) / 255U; } /* scale: 256 = 그대로, 그보다 작으면 그 비율만큼 줄임 (고정소수점 8bit) */ uint32_t scale = 256U; if (est_ma > WS_MAX_TOTAL_MA) { scale = (WS_MAX_TOTAL_MA * 256U) / est_ma; } __disable_irq(); for (uint32_t i = 0; i < NUM_LEDS; i++) { ws2812_send_byte((uint8_t)((led_grb[i][0] * scale) >> 8)); /* G */ ws2812_send_byte((uint8_t)((led_grb[i][1] * scale) >> 8)); /* R */ ws2812_send_byte((uint8_t)((led_grb[i][2] * scale) >> 8)); /* B */ } __enable_irq(); /* Reset/Latch(>=50us Low)은 다음 프레임까지의 HAL_Delay 구간이 충분히 커버함 */ } /* 0~255 위치값을 무지개 색상(R,G,B)으로 변환 (Adafruit NeoPixel wheel과 동일) */ static void wheel(uint8_t pos, uint8_t *r, uint8_t *g, uint8_t *b) { pos = 255 - pos; if (pos < 85) { *r = 255 - pos * 3; *g = 0; *b = pos * 3; } else if (pos < 170) { pos -= 85; *r = 0; *g = pos * 3; *b = 255 - pos * 3; } else { pos -= 170; *r = pos * 3; *g = 255 - pos * 3; *b = 0; } } int main(void) { /* USER CODE BEGIN 1 */ uint32_t count = 0; /* 프레임 카운터 (하트비트 타이밍용) */ uint32_t anim_offset = 0; /* 무지개 애니메이션 오프셋 (WS_ANIM_STEP만큼씩 증가) */ #if WS_TEST_ORIENTATION /* 방향 확인용: 1번(index 0) LED만 흰색, 나머지는 꺼둠 */ for (uint32_t i = 0; i < NUM_LEDS; i++) { uint8_t v = (i == 0) ? WS_BRIGHTNESS : 0; led_grb[i][0] = v; /* G */ led_grb[i][1] = v; /* R */ led_grb[i][2] = v; /* B */ } ws2812_show(); #else for (uint32_t i = 0; i < NUM_LEDS; i++) { uint8_t wheel_pos = (uint8_t)((i * 256 / NUM_LEDS + anim_offset) & 0xFF); uint8_t r, g, b; wheel(wheel_pos, &r, &g, &b); led_grb[i][0] = (uint8_t)((g * WS_BRIGHTNESS) / 255); led_grb[i][1] = (uint8_t)((r * WS_BRIGHTNESS) / 255); led_grb[i][2] = (uint8_t)((b * WS_BRIGHTNESS) / 255); } ws2812_show(); #endif } |