pico 펌웨어 올리기 여기서 pico에 펌웨어를 올렸기 때문에 KMDF로 USB Vendor Interface가 호환되는 드라이버를 만들어서 올렸다. 드라이버 올리는 것은 문제가 없었지만 endpoint로 write 하는 과정에서 문제가 발생하고 해결한 내용에 대해 포스팅하겠다.

PnP 매니저가 새 하드웨어를 감지하면 디바이스 생성을 요청한다. 드라이버는 WDFDEVICE 객체를 만들고 EvtDevicePrepareHardware 콜백을 등록한다.
이 단계가 중요하다. 시스템 디바이스에 I/O 리소스를 할당한 후 호출된다. 드라이버는 이 콜백에서 USB 인터페이스와 엔드포인트 정보를 얻을 수 있다. 여기서 device conetxt에 앞으로 통신에 활용할 Pipe를 저장한다. 이렇게 해야 이후에 IOCTL 요청이 들어왔을 때, Context에서 꺼내 Bulk 통신을 진행할 수 있다.

user mode에서 IOCTL 요청으로 led를 제어할 수 있도록 간단하게 코드를 작성했다.
int main()
{
std::cout << "Pico Driver Test Application" << std::endl;
std::cout << "=============================" << std::endl;
// Find and open the pico driver device
HANDLE hDevice = FindPicoDriverDevice();
if (hDevice != INVALID_HANDLE_VALUE) {
std::cout << "\nDriver handle obtained successfully!" << std::endl;
// LED Control Examples
DWORD bytesReturned = 0;
UCHAR command = 0;
UCHAR status = 0;
// Turn LED ON
std::cout << "\nTurning LED ON..." << std::endl;
command = LED_ON;
if (DeviceIoControl(hDevice, IOCTL_PICO_TEST_WRITE, &command, sizeof(command), NULL, 0, &bytesReturned, NULL)) {
std::cout << "LED ON - Success!" << std::endl;
} else {
std::cerr << "LED ON - Failed: " << GetLastError() << std::endl;
}
Sleep(1500);
// Turn LED OFF
std::cout << "\nTurning LED OFF..." << std::endl;
command = LED_OFF;
if (DeviceIoControl(hDevice, IOCTL_PICO_TEST_WRITE, &command, sizeof(command), NULL, 0, &bytesReturned, NULL)) {
std::cout << "LED OFF - Success!" << std::endl;
} else {
std::cerr << "LED OFF - Failed: " << GetLastError() << std::endl;
}
CloseHandle(hDevice);
} else {
std::cerr << "\nFailed to find or open the pico driver device." << std::endl;
return 1;
}
MessageBox(NULL, L"test", L"test", 0);
return 0;
}
FindPicoDriverDevice() 이 함수는 root_path/include/utils.hpp 에 작성해두었다. GUID를 활용하여 Device Handle을 얻어오는 함수이다.
빌드를 한 후 vm으로 파일을 옮겨서 테스트 해보았다.
LED를 켜기 위해서는 0x01 1바이트를 던져주면 되는데 pico에서 반응이 없었다. 그리고 device handle을 얻은 뒤 최초 ioctl 요청은 성공했는데 두 번째 요청에서 timeout을 걸어도 아무런 반응이 없었다. windbg로 디버깅을 해도 잡을 수 없었다.
일단 첫 번째 문제는 하드웨어 타겟에 맞지 않는 PIN 번호 설정이었다. 실제로 내가 산 pico 보드는 Waveshare RP2040 Zero 모델인데 기본 Pico 보드의 내장 led pin을 사용하고 있어서 25 -> 16으로 변경해주었다.
일반적인 LED는 단순히 High/Low 신호만 주면 켜지지만, WS2812B ARGB는 데이터라인 하나로 색상 정보를 직렬 전송해야 하는 프로토콜 기반 LED이다. 단순히 핀을 켜는 것이 아니라, Pico SDK에서 제공하는 PIO나 전용 라이브러리를 사용하여 타이밍에 맞춰 데이터를 밀어 넣어줘야 한다.
보드가 정상적으로 동작하고 로드가 되었는지 확인하기 위해서 LED 점멸 3회를 하도록 코드에 넣어줬다.
for (int i = 0; i < 3; i++) {
// Turn LED on
led_on();
sleep_ms(200); // Wait 200ms with LED on
// Turn LED off
led_off();
sleep_ms(200); // Wait 200ms with LED off
}
첫 번째 LED ON 명령: 성공
두 번째 LED OFF 명령: 무한 대기 (타임아웃)
TinyUSB의 OUT endpont 수신 콜백에서 하드웨어 버퍼를 명시적으로 비우지 않아 발생한 문제이다.
// 기존
void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) {
// buffer 파라미터로만 데이터 읽음
uint8_t command = buffer[0];
// ❌ 하드웨어의 실제 버퍼는 여전히 "데이터 있음" 상태
// ❌ OUT endpoint는 "아직 처리 중" 상태로 유지됨
}
// 수정 후
void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) {
// Explicitly read from buffer to clear hardware buffer and arm OUT endpoint
uint8_t dummy[64];
tud_vendor_n_read(itf, dummy, bufsize);
// Extract command byte from read data
uint8_t command = dummy[0];
// Queue command for main loop to process
// This avoids blocking operations in USB interrupt handler
if (command <= 0x02) { // Valid commands: 0x00, 0x01, 0x02
pending_command = command;
}
}
tud_vendor_n_read 을 명시적으로 호출해 주어서 매개변수로 받아온 버퍼의 데이터를 임시 버퍼로 옮겨준다. 물론 성능을 위해서는 최소한의 copy를 해야 하지만 지금은 테스트성으로 만드는 것이므로 추후에 성능 최적화는 할 예정이다.
하드웨어 버퍼를 명시적으로 비우니 정상적으로 동작이 되었다. 간단하게 MFC로 만들어서 테스트도 해보았다.
https://github.com/wangki-kyu/pico_usb_vendor
https://github.com/wangki-kyu/pico_driver