embeded/Cortex-M3 STM2026. 9. 23. 14:17

아두이노 예제밖에 안보이냐..

[링크 : https://mobilitykids.tistory.com/entry/MAX7219-LED-매트릭스-디스플레이Arduino-튜토리얼-포함]

[링크 : https://m.blog.naver.com/eduino/222651170585]

 

spi 같긴한데.. 원래는 7seg 용으로 개발되었으나 dot matrix에서도 쓰는건가?

Features
● 10MHz Serial Interface
● Individual LED Segment Control
● Decode/No-Decode Digit Selection
● 150μA Low-Power Shutdown (Data Retained)
● Digital and Analog Brightness Control
● Display Blanked on Power-Up
● Drive Common-Cathode LED Display
● Slew-Rate Limited Segment Drivers for Lower EMI (MAX7221)
● SPI, QSPI, MICROWIRE Serial Interface (MAX7221)

[링크 : https://www.analog.com/media/en/technical-documentation/data-sheets/max7219-max7221.pdf]

 

+

선도 연결방법 알려달라고 하고

어떻게 어떻게 해서  claude가 다 해줘버리네. 인간은 멀 취미로 해야하지?

ai가 해준걸 취미라고 해도 될까? 고민되네..

 

/*
 * max7219.h
 *
 * MAX7219 8x8 도트매트릭스(데이지체인) 구동 + 텍스트 스크롤 드라이버.
 * SPI(하드웨어)로 DIN/CLK를 보내고, CS(LOAD)는 별도 GPIO로 직접 제어한다.
 */
#ifndef __MAX7219_H
#define __MAX7219_H

#ifdef __cplusplus
extern "C" {
#endif

#include "stm32f1xx_hal.h"

/* 체인에 연결된 8x8 모듈 개수. 모듈 1개만 있으면 1로 바꿀 것. */
#define MAX7219_NUM_DEVICES   4

/* 하드웨어 배선에 따라 좌우/모듈 순서가 반대로 보이면 아래 두 값을 바꿔서 조정 */
#define MAX7219_REVERSE_CHAIN 1   /* 텍스트가 모듈 순서상 반대로 표시되면 1 (실측: 4 3 2 1로 나와서 1로 설정) */
#define MAX7219_MIRROR_COLS   0   /* 글자가 좌우 반전되어 보이면 1 */

void MAX7219_Init(SPI_HandleTypeDef *hspi);
void MAX7219_Clear(void);
void MAX7219_SetIntensity(uint8_t level /* 0x0~0xF */);

/* 표시할 문자열을 설정(내부 컬럼 버퍼에 렌더링해 둠). ASCII만 지원(공백,0-9,A-Z,.,!,-) */
void MAX7219_SetText(const char *text);

/* 한 프레임(1컬럼) 스크롤 진행 + 출력. 텍스트가 한 바퀴 돌면 1을 반환(그 외 0). */
uint8_t MAX7219_ScrollStep(void);

/* 진단용: 스크롤 없이 모듈(0번부터)마다 고정 숫자 1,2,3,4...를 동시에 정적으로 표시.
 * 다중 디바이스 주소 지정이 정상인지 확인할 때만 임시로 사용. */
void MAX7219_ShowDeviceIndexTest(void);

#ifdef __cplusplus
}
#endif

#endif /* __MAX7219_H */

 

/*
 * max7219.c
 *
 * MAX7219 8x8 도트매트릭스 체인 구동 + 좌->우 텍스트 스크롤.
 *
 * 배선(하드웨어 SPI1 사용):
 *   MAX7219 DIN  -> PA7 (SPI1_MOSI)
 *   MAX7219 CLK  -> PA5 (SPI1_SCK)
 *   MAX7219 CS   -> PA4 (일반 GPIO, 소프트웨어 제어)
 *   MAX7219 VCC  -> 5V,  GND -> GND
 *
 * 레지스터 모델: MAX7219는 디코드모드 OFF 상태에서 DIGIT0~7 레지스터 각각이
 * 매트릭스의 "행(row)"이며, 그 8bit가 그 행의 8개 열(column)을 나타낸다.
 * 따라서 프레임을 만들 때는 (행, 각 모듈별 8bit) 형태로 SPI에 실어야 한다.
 */
#include "main.h"
#include "max7219.h"
#include <string.h>
#include <ctype.h>

#define MAX7219_REG_NOOP        0x00
#define MAX7219_REG_DIGIT0      0x01
#define MAX7219_REG_DECODEMODE  0x09
#define MAX7219_REG_INTENSITY   0x0A
#define MAX7219_REG_SCANLIMIT   0x0B
#define MAX7219_REG_SHUTDOWN    0x0C
#define MAX7219_REG_DISPLAYTEST 0x0F

#define MAX7219_TOTAL_COLS      (MAX7219_NUM_DEVICES * 8)

/* 표시 가능한 최대 문자열 길이(글자당 6컬럼 = 5폭+1간격) + 앞뒤 여백 */
#define MAX7219_TEXT_MAX_CHARS  64
#define MAX7219_COLBUF_SIZE     (MAX7219_TOTAL_COLS * 2 + MAX7219_TEXT_MAX_CHARS * 6)

static SPI_HandleTypeDef *s_hspi;
static uint8_t  s_colbuf[MAX7219_COLBUF_SIZE];
static uint32_t s_colbuf_len;
static uint32_t s_scroll_offset;

/* ---- 5x7 폰트: 행(row) 단위 원본 표현. 각 바이트의 bit4..bit0 = 좌->우 5개 컬럼 ---- */
typedef struct { char ch; uint8_t rows[7]; } Glyph5x7;

static const Glyph5x7 s_font[] = {
  {' ', {0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000}},
  {'0', {0b01110,0b10001,0b10011,0b10101,0b11001,0b10001,0b01110}},
  {'1', {0b00100,0b01100,0b00100,0b00100,0b00100,0b00100,0b01110}},
  {'2', {0b01110,0b10001,0b00001,0b00010,0b00100,0b01000,0b11111}},
  {'3', {0b11111,0b00010,0b00100,0b00010,0b00001,0b10001,0b01110}},
  {'4', {0b00010,0b00110,0b01010,0b10010,0b11111,0b00010,0b00010}},
  {'5', {0b11111,0b10000,0b11110,0b00001,0b00001,0b10001,0b01110}},
  {'6', {0b00110,0b01000,0b10000,0b11110,0b10001,0b10001,0b01110}},
  {'7', {0b11111,0b00001,0b00010,0b00100,0b01000,0b01000,0b01000}},
  {'8', {0b01110,0b10001,0b10001,0b01110,0b10001,0b10001,0b01110}},
  {'9', {0b01110,0b10001,0b10001,0b01111,0b00001,0b00010,0b01100}},
  {'A', {0b01110,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}},
  {'B', {0b11110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110}},
  {'C', {0b01111,0b10000,0b10000,0b10000,0b10000,0b10000,0b01111}},
  {'D', {0b11110,0b10001,0b10001,0b10001,0b10001,0b10001,0b11110}},
  {'E', {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b11111}},
  {'F', {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b10000}},
  {'G', {0b01111,0b10000,0b10000,0b10011,0b10001,0b10001,0b01111}},
  {'H', {0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}},
  {'I', {0b01110,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110}},
  {'J', {0b00111,0b00010,0b00010,0b00010,0b00010,0b10010,0b01100}},
  {'K', {0b10001,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001}},
  {'L', {0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b11111}},
  {'M', {0b10001,0b11011,0b10101,0b10101,0b10001,0b10001,0b10001}},
  {'N', {0b10001,0b11001,0b10101,0b10101,0b10011,0b10001,0b10001}},
  {'O', {0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}},
  {'P', {0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000}},
  {'Q', {0b01110,0b10001,0b10001,0b10001,0b10101,0b10010,0b01101}},
  {'R', {0b11110,0b10001,0b10001,0b11110,0b10100,0b10010,0b10001}},
  {'S', {0b01111,0b10000,0b10000,0b01110,0b00001,0b00001,0b11110}},
  {'T', {0b11111,0b00100,0b00100,0b00100,0b00100,0b00100,0b00100}},
  {'U', {0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}},
  {'V', {0b10001,0b10001,0b10001,0b10001,0b10001,0b01010,0b00100}},
  {'W', {0b10001,0b10001,0b10001,0b10101,0b10101,0b10101,0b01010}},
  {'X', {0b10001,0b10001,0b01010,0b00100,0b01010,0b10001,0b10001}},
  {'Y', {0b10001,0b10001,0b01010,0b00100,0b00100,0b00100,0b00100}},
  {'Z', {0b11111,0b00001,0b00010,0b00100,0b01000,0b10000,0b11111}},
  {'.', {0b00000,0b00000,0b00000,0b00000,0b00000,0b01100,0b01100}},
  {'!', {0b00100,0b00100,0b00100,0b00100,0b00100,0b00000,0b00100}},
  {'-', {0b00000,0b00000,0b00000,0b11111,0b00000,0b00000,0b00000}},
};
#define FONT_COUNT (sizeof(s_font) / sizeof(s_font[0]))

static const Glyph5x7 *font_lookup(char c)
{
  if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A'); /* 소문자 -> 대문자 */
  for (uint32_t i = 0; i < FONT_COUNT; i++)
  {
    if (s_font[i].ch == c) return &s_font[i];
  }
  return &s_font[0]; /* 지원 안 하는 문자는 공백으로 */
}

/* row(0=top..6=bottom) 문자열 비트에서 col(0=left..4)에 해당하는 컬럼 바이트를 뽑아
 * bit r 위치에 채운다 (bit0=top row ... bit6=bottom row). */
static uint8_t glyph_col_byte(const Glyph5x7 *g, uint8_t col)
{
  uint8_t val = 0;
  for (uint8_t r = 0; r < 7; r++)
  {
    uint8_t bit = (g->rows[r] >> (4 - col)) & 0x01U;
    val |= (uint8_t)(bit << r);
  }
  return val;
}

/* ---- 저수준 SPI 전송 ---- */

static inline void cs_low(void)  { HAL_GPIO_WritePin(MAX7219_CS_PORT, MAX7219_CS_PIN, GPIO_PIN_RESET); }
static inline void cs_high(void) { HAL_GPIO_WritePin(MAX7219_CS_PORT, MAX7219_CS_PIN, GPIO_PIN_SET); }

/* 체인의 모든 모듈에 동일한 (reg,data)를 전송 (초기화/공통 설정용) */
static void max7219_write_all(uint8_t reg, uint8_t data)
{
  uint8_t buf[2] = { reg, data };
  cs_low();
  for (uint32_t i = 0; i < MAX7219_NUM_DEVICES; i++)
  {
    HAL_SPI_Transmit(s_hspi, buf, 2, HAL_MAX_DELAY);
  }
  cs_high();
}

/* 한 행(row 0~7)에 대해 모듈별 8bit 데이터를 체인에 맞는 순서로 전송.
 * dev_data[0] = MCU와 가장 가까운(첫번째) 모듈. SPI 시프트 특성상 체인에서는
 * 가장 먼 모듈 데이터부터 먼저 밀어넣어야 CS 상승 시 올바르게 래치된다. */
static void max7219_send_row(uint8_t row, const uint8_t *dev_data)
{
  uint8_t reg = (uint8_t)(MAX7219_REG_DIGIT0 + row);
  cs_low();
#if MAX7219_REVERSE_CHAIN
  for (int32_t dev = 0; dev < MAX7219_NUM_DEVICES; dev++)
#else
  for (int32_t dev = MAX7219_NUM_DEVICES - 1; dev >= 0; dev--)
#endif
  {
    uint8_t buf[2] = { reg, dev_data[dev] };
    HAL_SPI_Transmit(s_hspi, buf, 2, HAL_MAX_DELAY);
  }
  cs_high();
}

void MAX7219_Init(SPI_HandleTypeDef *hspi)
{
  s_hspi = hspi;

  max7219_write_all(MAX7219_REG_DISPLAYTEST, 0x00);
  max7219_write_all(MAX7219_REG_SCANLIMIT, 0x07);   /* 8자리(행) 전부 사용 */
  max7219_write_all(MAX7219_REG_DECODEMODE, 0x00);  /* BCD 디코드 없음: 매트릭스 모드 */
  max7219_write_all(MAX7219_REG_INTENSITY, 0x04);   /* 밝기 0x0~0xF */
  max7219_write_all(MAX7219_REG_SHUTDOWN, 0x01);    /* 0=shutdown, 1=normal operation */

  MAX7219_Clear();
}

void MAX7219_SetIntensity(uint8_t level)
{
  max7219_write_all(MAX7219_REG_INTENSITY, (uint8_t)(level & 0x0FU));
}

void MAX7219_Clear(void)
{
  uint8_t zeros[MAX7219_NUM_DEVICES];
  memset(zeros, 0, sizeof(zeros));
  for (uint8_t row = 0; row < 8; row++)
  {
    max7219_send_row(row, zeros);
  }
}

void MAX7219_SetText(const char *text)
{
  uint32_t len = 0;

  /* 화면 밖에서 스크롤 들어오도록 앞쪽 여백(패널 폭만큼 공백) */
  for (uint32_t i = 0; i < MAX7219_TOTAL_COLS && len < MAX7219_COLBUF_SIZE; i++)
  {
    s_colbuf[len++] = 0x00;
  }

  for (const char *p = text; *p != '\0' && len + 6 <= MAX7219_COLBUF_SIZE; p++)
  {
    const Glyph5x7 *g = font_lookup(*p);
    for (uint8_t c = 0; c < 5; c++)
    {
      s_colbuf[len++] = glyph_col_byte(g, c);
    }
    s_colbuf[len++] = 0x00; /* 글자 사이 1컬럼 간격 */
  }

  /* 화면 밖으로 완전히 빠져나가도록 뒤쪽 여백 */
  for (uint32_t i = 0; i < MAX7219_TOTAL_COLS && len < MAX7219_COLBUF_SIZE; i++)
  {
    s_colbuf[len++] = 0x00;
  }

  s_colbuf_len = len;
  s_scroll_offset = 0;
}

static void render_frame(void)
{
  uint8_t dev_row[MAX7219_NUM_DEVICES];

  if (s_colbuf_len == 0) return;

  for (uint8_t row = 0; row < 8; row++)
  {
    for (uint32_t dev = 0; dev < MAX7219_NUM_DEVICES; dev++)
    {
      uint8_t val = 0;
      for (uint8_t c = 0; c < 8; c++)
      {
        uint32_t col_index = (s_scroll_offset + dev * 8U + c) % s_colbuf_len;
        uint8_t bit = (s_colbuf[col_index] >> row) & 0x01U;
#if MAX7219_MIRROR_COLS
        val |= (uint8_t)(bit << c);
#else
        val |= (uint8_t)(bit << (7 - c));
#endif
      }
      dev_row[dev] = val;
    }
    max7219_send_row(row, dev_row);
  }
}

uint8_t MAX7219_ScrollStep(void)
{
  render_frame();

  if (s_colbuf_len == 0) return 1;

  s_scroll_offset++;
  if (s_scroll_offset >= s_colbuf_len)
  {
    s_scroll_offset = 0;
    return 1;
  }
  return 0;
}

/* 각 모듈에 스크롤 없이 고정 숫자를 동시에 정적으로 표시(디버그용).
 * dev=0 -> "1", dev=1 -> "2" ... 모듈이 서로 다른 내용을 "동시에" 보여주는지,
 * 그리고 계속 켜진 상태로 유지되는지(깜빡임/순차갱신 없는지) 확인하는 용도. */
void MAX7219_ShowDeviceIndexTest(void)
{
  uint8_t dev_row[MAX7219_NUM_DEVICES];
  const Glyph5x7 *glyphs[MAX7219_NUM_DEVICES];

  for (uint32_t dev = 0; dev < MAX7219_NUM_DEVICES; dev++)
  {
    char digit = (char)('1' + (dev % 9));
    glyphs[dev] = font_lookup(digit);
  }

  for (uint8_t row = 0; row < 8; row++)
  {
    for (uint32_t dev = 0; dev < MAX7219_NUM_DEVICES; dev++)
    {
      uint8_t val = 0;
      if (row < 7) /* 글꼴은 7행(0~6)까지만 있음, 8번째 행은 항상 빈 줄 */
      {
        for (uint8_t c = 0; c < 5; c++) /* 글꼴 폭 5, 나머지 3컬럼은 여백(0) */
        {
          uint8_t bit = (glyphs[dev]->rows[row] >> (4 - c)) & 0x01U;
#if MAX7219_MIRROR_COLS
          val |= (uint8_t)(bit << (2 + c));
#else
          val |= (uint8_t)(bit << (7 - c));
#endif
        }
      }
      dev_row[dev] = val;
    }
    max7219_send_row(row, dev_row);
  }
}

'embeded > Cortex-M3 STM' 카테고리의 다른 글

ws2812b vs hub75  (0) 2026.09.23
네오픽셀 ws2812b-64 작동  (0) 2026.09.23
stm32 gpio hi-z  (0) 2026.09.10
stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
stm32 usb composite / report id  (0) 2026.09.04
Posted by 구차니
embeded/Cortex-M3 STM2026. 9. 23. 12:46

짱구 굴려봐도.. ws2812b가 절대 쌀수가 없네..

라즈베리 파이에 ws2612b-64를 8x8로 구성해서

총 64x64 해상도 출력하는데 드는 돈이 장당 2500원 해도 16만원 (실 구매가는 2200원 이긴 한데)

전원이랑 라즈베리 파이 추가하면 총 가격이 어마어마 해진다.

 

[링크 : https://itempage3.auction.co.kr/DetailView.aspx?itemno=F479040772] HUB75 128x128 13만원

 

 네, 보통 HUB75가 단위면적당 더 쌉니다. 이유는 구조적입니다.

  근본 이유
  - WS2812B: LED 하나당 드라이버 IC가 하나씩 내장(개별 어드레싱) → LED 개수만큼 IC 비용이 그대로 곱해짐
  - HUB75: LED 여러 개(보통 16개)를 드라이버 IC 하나(MBI5124 등)가 묶어서 구동 → IC 개수가 LED 개수의 1/16
    수준이라 원가가 확 낮음
  - 게다가 HUB75는 옥외광고/전광판 산업에서 이미 수백만 장 양산되는 표준품이라 규모의 경제까지 붙음

  대략적인 단가 비교 (픽셀피치 1cm 기준으로 맞춰서)
  - WS2812B 8×8 모듈(8cm×8cm=64cm²): 약 2,500원 → 약 39원/cm²
  - HUB75 P10 판넬(32×16px, 32cm×16cm=512cm²): 알리 기준 대략 10,000~15,000원 → 약 20~29원/cm²

  → 같은 면적, 비슷한 해상도 밀도 기준으로 HUB75가 30~50% 정도 저렴한 편입니다.

  64×64(64cm×64cm) 전체로 환산하면
  - WS2812B: 아까 계산한 16만원
  - HUB75(같은 픽셀피치 P10 기준, 32×32px 판넬 4장 정도 조합): 대략 4~6만원대로 절반 이하까지 내려갈 수 있음

  덤으로 따라오는 이점
  - HUB75 판넬은 애초에 체이닝용 커넥터 + 판넬 내 전원 분배가 설계되어 있어서, 아까 얘기한 "전원 주입 지점 직접
    설계" 부담이 훨씬 줄어듭니다 (완전히 없어지진 않지만 DIY WS2812보다 훨씬 정형화됨)

  대신 치러야 할 비용은 복잡도
  - 드라이버가 HUB75 전용 스캔+BCM 방식이라 소프트웨어를 새로 짜야 함 (지금 있는 WS2812 코드 재사용 불가, 전에
    말씀드린 대로)
  - MCU도 RPi나 ESP32-S3처럼 DMA로 지속 스캔 감당할 수 있는 걸 써야 함

  정리하면 돈은 HUB75가 이기고, 개발 난이도는 WS2812가 더 쉬움 — 트레이드오프입니다. 64개 패널 사기 전에 HUB75
  견적도 한 번 비교해보시는 게 좋을 것 같아요.

'embeded > Cortex-M3 STM' 카테고리의 다른 글

max7219 8-Digit LED Display Drivers  (0) 2026.09.23
네오픽셀 ws2812b-64 작동  (0) 2026.09.23
stm32 gpio hi-z  (0) 2026.09.10
stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
stm32 usb composite / report id  (0) 2026.09.04
Posted by 구차니
embeded/Cortex-M3 STM2026. 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
}

 

 

'embeded > Cortex-M3 STM' 카테고리의 다른 글

max7219 8-Digit LED Display Drivers  (0) 2026.09.23
ws2812b vs hub75  (0) 2026.09.23
stm32 gpio hi-z  (0) 2026.09.10
stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
stm32 usb composite / report id  (0) 2026.09.04
Posted by 구차니
embeded/Cortex-M3 STM2026. 9. 10. 21:41

흐음...?

일단 gpio 읽을 row high + col read 조합으로 하는데 (풀때는 row low)

col 하나가 눌리면 다른 row에서 해당 col은 읽히지가 않아서 찾아보니

high-z로 가야지 읽을수 있다고 한다. 테스트는 필요함

 

by claude

STM32 HAL에는 "Hi-Z로 쓰기" 함수가 없어서, 핀 모드를 **입력(No Pull)**으로 바꿔야 Hi-Z가 됩니다. 행을 스캔할 때만 출력으로 바꾸고, 끝나면 다시 입력으로 돌리면 됩니다.


static void row_pin_mode(uint16_t pin, uint32_t mode)
{
    GPIO_InitTypeDef init = {0};
    init.Pin   = pin;
    init.Mode  = mode;
    init.Pull  = GPIO_NOPULL;
    init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &init);
}



static inline void pin_hiz(GPIO_TypeDef *port, uint16_t pin)
{
    uint32_t pos = __builtin_ctz(pin) * 2U;
    port->MODER &= ~(0x3U << pos);                               // 00: Input (Hi-Z)
}

static inline void pin_output(GPIO_TypeDef *port, uint16_t pin)
{
    uint32_t pos = __builtin_ctz(pin) * 2U;
    port->MODER = (port->MODER & ~(0x3U << pos)) | (0x1U << pos); // 01: Output
}

 

 

by chatgpt

GPIOA->CRL &= ~(0xF << (1 * 4));
GPIOA->CRL |=  (0x4 << (1 * 4));

여기서 0x4는:

MODE = 00  → Input
CNF  = 01  → Floating input

여러 핀을 한꺼번에

예를 들어 PA1, PA2, PA3을 모두 Hi-Z로:

GPIOA->CRL &= ~(
    (0xF << (1 * 4)) |
    (0xF << (2 * 4)) |
    (0xF << (3 * 4))
);

GPIOA->CRL |= (
    (0x4 << (1 * 4)) |
    (0x4 << (2 * 4)) |
    (0x4 << (3 * 4))
);

'embeded > Cortex-M3 STM' 카테고리의 다른 글

ws2812b vs hub75  (0) 2026.09.23
네오픽셀 ws2812b-64 작동  (0) 2026.09.23
stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
stm32 usb composite / report id  (0) 2026.09.04
로터리 엔코더.. 2?  (0) 2026.08.31
Posted by 구차니
embeded/Cortex-M3 STM2026. 9. 8. 16:22

귀찮으니 오늘도 대충대충

그나저나 고스팅이란걸 겪어보니 참 난해하구만?

 

핀은 아래처럼 구성하고

 

C1~C4는 A0~A3에 연결함 (C1을 LSB로 보게 하기 위해서)

 

코드는 효율적이진 못하지만 일단 대충 이런느낌으로 작성

row를 high / low로 바꾸면서 col을 읽는구조로 해놨고(col - pin 0~3, row - pin 4~7)

#define BUF_SIZE 50
uint8_t usb_buf[BUF_SIZE];
uint32_t count = 0;
#if 1
int prev_val = 0;
void gpio_proc()
{
  int val = 0;
  // row
  // val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) << 4);
  // val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) << 5);
  // val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) << 6);
  // val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) << 7);

  char *swname[4][4] = {
    {"S1", "S2", "S3", "S4"},
    {"S5", "S6", "S7", "S8"},
    {"S9", "S10", "S11", "S12"},
    {"S13", "S14", "S15", "S16"}
  };

  for(uint16_t row_idx = 0; row_idx < 4; row_idx ++)
  {
    uint16_t pin_arr[] = {GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7};
    HAL_GPIO_WritePin(GPIOA, pin_arr[row_idx], GPIO_PIN_SET);
    HAL_Delay(1);

    // column
    val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) << 0);
    val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) << 1);
    val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) << 2);
    val |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) << 3);

    HAL_GPIO_WritePin(GPIOA, pin_arr[row_idx], GPIO_PIN_RESET);

    int col_idx = 0;
    for(int col_idx = 0; col_idx < 4; col_idx++)
    {
      if((val >> col_idx) & 0x01)
      {
        char *name = swname[row_idx][col_idx];
        sprintf((char*)usb_buf + strlen(usb_buf), "%s ", name);
      }
    }

    val = 0;
  }

  sprintf((char*)usb_buf + strlen(usb_buf), " --- %d %X\r\n", count, val);
  CDC_Transmit_FS(usb_buf, BUF_SIZE);
  memset(usb_buf,0,BUF_SIZE);
  count++;

}
#endif

 

한 행에 누르면 전부 인식되지만 (열은 구분 문제 없음)

 

한 열에 누르면 (S4 + S8 + S12)

해당 라인 자체가 눌리지 않은것으로 나온다. 이래서 다이오드 다는건가?

'embeded > Cortex-M3 STM' 카테고리의 다른 글

네오픽셀 ws2812b-64 작동  (0) 2026.09.23
stm32 gpio hi-z  (0) 2026.09.10
stm32 usb composite / report id  (0) 2026.09.04
로터리 엔코더.. 2?  (0) 2026.08.31
bluepill / 로터리 엔코더 테스트  (0) 2026.08.28
Posted by 구차니
embeded/Cortex-M3 STM2026. 9. 4. 10:30

composite는 별도 소스를 넣어야지 cubeMX를 통해서 지원하지 않은것 같고

report id 쪽은 좀더 봐야 알 것 같은데

얘도 cubeMX로 하는건 아닌것 같은 느낌이..

 

[링크 : https://controllerstech.com/stm32-usb-hid-mouse-and-keyboard-combined/] report id 이용

[링크 : https://controllerstech.com/stm32-usb-composite-class-cdc-hid/] 복합클래스

    [링크 : https://controllerstech.com/stm32-usb-hid-mouse-keyboard/]

 

 

'embeded > Cortex-M3 STM' 카테고리의 다른 글

stm32 gpio hi-z  (0) 2026.09.10
stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
로터리 엔코더.. 2?  (0) 2026.08.31
bluepill / 로터리 엔코더 테스트  (0) 2026.08.28
stm32 port 전체 한번에 출력 변경하기  (0) 2026.06.22
Posted by 구차니
embeded/Cortex-M3 STM2026. 8. 31. 18:23

동료직원 괴롭혀서 실장!

R1_1에 10k가 없었는데 하나 달았다.

 

PB5 에 SW가 있고

 

 

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
void USB_CDC_RxHandler(uint8_t* Buf, uint32_t Len)
{
  for(int idx = 0; idx < Len;idx++)
  if(Buf[idx] == 0x0D) Buf[idx] = 0x0A;
CDC_Transmit_FS(Buf, Len);
}


#define BUF_SIZE 50
uint8_t usb_buf[BUF_SIZE];
uint32_t count = 0;

char prev_gpio = 0;
void gpio_proc()
{
  char curr_gpio = 0;
  GPIO_PinState gpios[3];
  curr_gpio |= (gpios[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3));
  curr_gpio |= (gpios[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)) << 1;
  curr_gpio |= (gpios[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)) << 2;

  char lut[4] = {0, 1, 3, 2};

  if(prev_gpio != curr_gpio)
  {
    char isCW = 0;
    char prev = lut[prev_gpio & 0x03];
    char curr = lut[curr_gpio & 0x03];
    if((prev  + 1) % 4 == curr)
    { isCW = 1; }

    sprintf((char*)usb_buf, "%ld %d %d %s, %d %s\r\n", count, gpios[1], gpios[0], isCW?"CW":"CCW",
     gpios[2], gpios[2]?"rel":"push");
    CDC_Transmit_FS(usb_buf, BUF_SIZE);
    memset(usb_buf,0,BUF_SIZE);
    count++;

    prev_gpio = curr_gpio;
  }
}

 

풀업되어있다가 누르면 떨어지는거라

1이면 안누른거

0이면 누른걸로 인식된다.

35 1 0 CW, 1 rel
36 0 0 CW, 1 rel
37 0 1 CW, 1 rel
38 1 1 CW, 1 rel
39 1 0 CW, 1 rel
40 0 0 CW, 1 rel
41 0 1 CW, 1 rel
42 1 1 CW, 1 rel
43 0 1 CCW, 1 rel
44 0 0 CCW, 1 rel
45 1 0 CCW, 1 rel
46 1 1 CCW, 1 rel
47 0 1 CCW, 1 rel
48 0 0 CCW, 1 rel
49 1 0 CCW, 1 rel
50 1 1 CCW, 1 rel
51 0 1 CCW, 1 rel
52 0 0 CCW, 1 rel
53 0 0 CCW, 0 push
54 0 0 CCW, 1 rel

 

도대체 왜! 이거 부품이 안달려 있는거야?!

'embeded > Cortex-M3 STM' 카테고리의 다른 글

stm32f103c9t6(bluepill) + keypad  (0) 2026.09.08
stm32 usb composite / report id  (0) 2026.09.04
bluepill / 로터리 엔코더 테스트  (0) 2026.08.28
stm32 port 전체 한번에 출력 변경하기  (0) 2026.06.22
stm32 uart data bit  (0) 2026.03.17
Posted by 구차니
embeded/Cortex-M3 STM2026. 8. 28. 12:40

오래되서 기억도 안나네 ㅋㅋ

PB5 - SW (스위치) <- 먼가 오작동.  + 쪽 3V3과 연결되지 않고 따로 전원 연결해줘야 하는 듯.

PB4 - CLK (로터리)

PB3 - DT (로터리)

귀찮아서 USB CDC 를 통해서 출력하게 함.

#define BUF_SIZE 50
uint8_t usb_buf[BUF_SIZE];
uint32_t count = 0;
int prev_gpio = 0;
void gpio_proc()
{
  int curr_gpio = 0;
  GPIO_PinState gpios[3];
  curr_gpio |= (gpios[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3));
  curr_gpio |= (gpios[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)) << 1;
  curr_gpio |= (gpios[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)) << 2;

  if(prev_gpio != curr_gpio)
  {
    sprintf((char*)usb_buf, "%ld %d %d %d\r\n", count, gpios[2], gpios[1], gpios[0]);
    CDC_Transmit_FS(usb_buf, BUF_SIZE);
    memset(usb_buf,0,BUF_SIZE);
    count++;

    prev_gpio = curr_gpio;
  }
}

 

시계방향 회전. 두 번 딱딱 걸리고 다시 0 0 0  으로 돌아온다.

[2026-08-28 12:20:17.496] 1897 0 0 0                                            
[2026-08-28 12:20:20.417] 1898 0 0 1                                            
[2026-08-28 12:20:20.469] 1899 0 1 1                                            
[2026-08-28 12:20:20.735] 1900 0 1 0                                            
[2026-08-28 12:20:20.767] 1901 0 0 0

 

반시계방향 회전

[2026-08-28 12:20:20.767] 1901 0 0 0                                            
[2026-08-28 12:20:21.906] 1902 0 1 0                                            
[2026-08-28 12:20:21.959] 1903 0 1 1                                            
[2026-08-28 12:20:22.202] 1904 0 0 1                                            
[2026-08-28 12:20:22.233] 1905 0 0 0  

 

정방향 0 - 1 - 3 - 2 - 0 으로

역방향 0 - 2 - 3 - 1 - 0 으로

표현되긴한데.. gray code 같기도 하고?

[링크 : https://woodforest.tistory.com/147]

 

그래서 저 값을 가지고 판단을 하면 이제 CW 방향인지 CCW 방향인지 판단이 가능해진다.

char prev_gpio = 0;
void gpio_proc()
{
  char curr_gpio = 0;
  GPIO_PinState gpios[3];
  curr_gpio |= (gpios[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3));
  curr_gpio |= (gpios[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)) << 1;
  // curr_gpio |= (gpios[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)) << 2;

  char lut[4] = {0, 1, 3, 2};

  if(prev_gpio != curr_gpio)
  {
    char isCW = 0;
    char prev = lut[prev_gpio];
    char curr = lut[curr_gpio];
    if((prev  + 1) % 4 == curr)
    { isCW = 1; }

    sprintf((char*)usb_buf, "%ld %d %d %s\r\n", count, gpios[1], gpios[0], isCW?"CW":"CCW");
    CDC_Transmit_FS(usb_buf, BUF_SIZE);
    memset(usb_buf,0,BUF_SIZE);
    count++;

    prev_gpio = curr_gpio;
  }
}

 

테스트 끝!

[2026-08-28 12:33:57.522] 0 0 1 CW                                              
[2026-08-28 12:33:57.555] 1 1 1 CW                                              

[2026-08-28 12:33:58.521] 2 1 0 CW                                              
[2026-08-28 12:33:58.553] 3 0 0 CW                                              

[2026-08-28 12:33:59.452] 4 1 0 CCW                                             
[2026-08-28 12:33:59.496] 5 1 1 CCW                                             

[2026-08-28 12:34:00.156] 6 1 0 CW                                              
[2026-08-28 12:34:00.178] 7 0 0 CW                                              

[2026-08-28 12:34:00.729] 8 1 0 CCW // 반시계 방향 한번 돌리기
[2026-08-28 12:34:00.779] 9 1 1 CCW                                             

[2026-08-28 12:34:04.895] 10 0 1 CCW // 드륵 느낌이 안나게 반시계 방향으로 천천히 돌림

[2026-08-28 12:34:06.098] 11 1 1 CW    // 드륵 느낌이 날때까지 시계 방향으로 돌림

[2026-08-28 12:34:07.518] 12 0 1 CCW  // 드륵 느낌이 안나게 반시계 방향으로 천천히 돌림

[2026-08-28 12:34:08.590] 13 1 1 CW  // 드륵 느낌이 날때까지 시계 방향으로 돌림

[2026-08-28 12:34:09.858] 14 0 1 CCW                                            
[2026-08-28 12:34:09.916] 15 0 0 CCW                                            

[2026-08-28 12:34:11.124] 16 1 0 CCW                                            
[2026-08-28 12:34:11.154] 17 1 1 CCW                                            

[2026-08-28 12:34:12.087] 18 1 0 CW                                             
[2026-08-28 12:34:12.127] 19 0 0 CW                                             

[2026-08-28 12:34:12.444] 20 0 1 CW                                             
[2026-08-28 12:34:12.476] 21 1 1 CW                                             

[2026-08-28 12:34:12.673] 22 1 0 CW                                             
[2026-08-28 12:34:12.693] 23 0 0 CW                                             

[2026-08-28 12:34:12.746] 24 0 1 CW                                             
[2026-08-28 12:34:12.778] 25 1 1 CW                                             

[2026-08-28 12:34:12.913] 26 1 0 CW  

 

역시 딴짓은 시원한 에어컨과

회사 전기와, 월급을 받으면서 딴짓을 해야 잘됨 ㅋㅋㅋ

'embeded > Cortex-M3 STM' 카테고리의 다른 글

stm32 usb composite / report id  (0) 2026.09.04
로터리 엔코더.. 2?  (0) 2026.08.31
stm32 port 전체 한번에 출력 변경하기  (0) 2026.06.22
stm32 uart data bit  (0) 2026.03.17
stm32 다른 영역으로 점프(부트로더)  (0) 2026.03.06
Posted by 구차니
embeded/Cortex-M3 STM2026. 6. 22. 12:21

AVR 때는 자주 쓴거 같은데

stm32 되면서 함수를 통해 1개 핀에 대해서만 바꾸다 보니 불편해서 찾아보는데

머.. 결국은 레지스터 건드릴 수 밖에 없는 듯

 

ODR 값 읽고 마스킹 후에 한번에 갈아 치우는게 나을 듯

If you want the whole port (all 16 pins) get the value, then you simply write it to the ODR register:
GPIOB->ODR = 0xFFFF; //writing 1/0 in a bit position, 
sets/resets that bit. ALL BITS ARE AFFECTED no matter the value your write in.

If you want to set some pins, but not altering the other pins, with one instruction:
GPIOB->BSRR = 0x00F; //writing 1 in a bit position, 
sets that bit leaves the others as they are

If you want to reset some pins, but not altering the other pins, with one instruction:
GPIOB->BRR = 0x00F; //writing 1 in a bit position, 
clears that bit and leaves the others as they are

If you want a "portion" of a GPIO port to get an specific value, this method is 2 instructions instead of "read-change-write":
GPIOB->BRR = 0x00F; //clear the first 4 bits
GPIOB->BSRR = 0x00A; //set the desired bits in that 4-bit field

 

 ODR 정도는 low level 함수로 존재하나 보다.

using LL API:

LL_GPIO_WriteOutputPort(GPIOA,x);

[링크 : https://electronics.stackexchange.com/questions/683401/can-i-write-a-8-bit-or-16-bit-data-to-the-outport-of-the-stm32-directly]

 

wtite_bits(uint16_t cmd)
{
    uint32_t data = GPIOA -> ODR;

    data &= ~(0x1fff);
    data |= cmd & 0x1fff;
    GPIOA -> ODR = data;

    data = GPIOB -> ODR;
    data &= ~(0x0007);
    data |= (cmd & 0x8fff) >> 13;
    GPIOB -> ODR = data;
}

[링크 : https://stackoverflow.com/questions/48365156/stm32f4-write-read-16bits-into-two-8-bit-gpio-port]

'embeded > Cortex-M3 STM' 카테고리의 다른 글

로터리 엔코더.. 2?  (0) 2026.08.31
bluepill / 로터리 엔코더 테스트  (0) 2026.08.28
stm32 uart data bit  (0) 2026.03.17
stm32 다른 영역으로 점프(부트로더)  (0) 2026.03.06
stm32cubeide cpp 변환이후 generate code  (0) 2026.02.25
Posted by 구차니
embeded/Cortex-M3 STM2026. 3. 17. 10:35

including parity가 좀 헷갈리는데

stm32g473 의 경우 data가 7bit / 8bit / 9bit을 지원한다.

 

f469, f103 은 8bit / 9bit 를 지원한다.

 

 

modbus ascii / 7bit data를 일부 stm32는 사용하지 못하고 stm32F4 나 L4 이후로는 가능한것으로 생각

[링크 : https://blog.naver.com/kim1417/222499708921]

 

정말로.. parity가 1비트 먹어서 data 9 bit 이면 parity none으로 밖에 못쓰나?

[링크 : https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf]

 

Posted by 구차니