본문 바로가기

ESP32

ESP-32 LVGL 그래픽을 사용한 고급 기술 - 2부

반응형

ESP-32 및 LVGL 그래픽을 사용한 고급 기술 - 2부

 

1부에서는 CrowPanel 2.4인치 디스플레이 장치의 프로그래밍 환경으로 Arduino IDE를 설정하고 구성하는 방법을 알아보고, 속도계 애플리케이션의 다이얼 표면을 표시하도록 디스플레이를 프로그래밍했습니다. 이번 강의에서는 스프라이트 애니메이션을 사용하여 아날로그 장치처럼 보이는 속도계 바늘을 표시하는 방법을 알아보겠습니다. 이 속도계 바늘은 아래 다이얼 표면 요소 위로 움직입니다. 먼저 스프라이트 그래픽 애니메이션에 대한 간략한 배경 지식을 살펴보겠습니다.

 

 

 

스프라이트 애니메이션

 

스프라이트 애니메이션은 1970년대 초부터 컴퓨터 그래픽 시스템에서 개발되어 사용되었습니다. 이는 초기 게임과 아케이드 시스템 개발의 핵심 요소였습니다. 더 중요한 것은, 가장 널리 보급된 컴퓨터 장치 중 하나인 팔로알토 연구 센터 에서 개발된 컴퓨터 마우스 개발의 핵심 요소였다는 것입니다. 컴퓨터 화면에서 마우스 포인터를 움직일 때마다 스프라이트 애니메이션이 적용됩니다. 이 용어는 원래 스프라이트가 유령이나 신화 속 스프라이트 처럼 배경 이미지를 덮어쓰지 않고 그 위에 "떠다니는" 모습에서 유래되었습니다 .

 

 

 

 

일반적으로 스프라이트는 고속 그래픽 조작 명령을 사용하여 기본 그래픽 이미지 위에 중첩할 수 있는 작은 2D 그래픽 이미지입니다. 기본 이미지를 기준으로 스프라이트의 위치와 방향을 변경하면 애니메이션 효과를 얻을 수 있습니다. 사용하는 TFT_eSPI 그래픽 라이브러리는 스프라이트 그래픽을 지원하며, 이를 사용하여 애플리케이션의 속도계 바늘을 만들 것입니다. 짐작하시겠지만, 스프라이트는 매우 사실적인 효과를 낼 수 있기 때문에 게임과 시뮬레이터에서도 널리 사용됩니다.

 

속도계 코딩

 

이번 레슨에서는 세 개의 스프라이트 객체를 사용하여 게이지에 원하는 그래픽 애니메이션 효과를 구현해 보겠습니다. 스프라이트를 결합하여 원하는 화면 이미지를 메모리에 생성한 후, 화면에 표시합니다. 그러면 깜빡임이나 지연 현상을 최소화하여 매우 빠르게 표시 작업이 완료됩니다. 이번 레슨에서는 속도계 다이얼 배경 그래픽을 제작했던 1부의 결과를 바탕으로 작업합니다. 새롭게 수행해야 할 단계는 다음과 같습니다.

 

1. 레슨 1에서처럼 속도계 면의 배경 이미지를 만듭니다. 이번에는 배경이라는 스프라이트 객체로 만들겠습니다.

 

2. 속도계 바늘을 포함하는 스프라이트 객체를 만들고 이름을 needle이라고 지정합니다. 표준 그래픽 명령을 사용하여 스프라이트 "공간"에 바늘 이미지를 생성합니다.

 

3. 이미지를 표시하기 전에 이미지를 빌드하기 위해 display라는 세 번째 스프라이트를 만듭니다. 이전 버전의 프로그램에서는 배경 스프라이트를 추가한 다음 바늘 스프라이트를 화면 이미지(tft)에 직접 추가했습니다. 그래픽은 정상적으로 표시되었지만 바늘 이미지에 눈에 띄는 깜빡임이 있었습니다. 디스플레이 스프라이트에 배경과 바늘을 함께 추가한 다음 디스플레이 스프라이트를 tft 디스플레이로 밀어넣자 깜빡임이 제거되었습니다. 단순화를 위해 배경과 디스플레이 스프라이트를 tft 화면과 같은 크기(240 x 320 픽셀)로 만들었지만 공간을 절약하기 위해 조금 더 작게(240 x 240) 만들 수도 있었습니다. 스프라이트가 작을수록 메모리와 처리 시간이 덜 걸리지만 이 경우에는 잘 작동하며 단순화에 따른 페널티도 없습니다.

 

4. 속도 입력 값을 기반으로 바늘의 각도 방향을 계산합니다. 아직 센서를 설치하지 않았으므로 가짜 속도 입력 신호를 생성합니다.

 

5. 디스플레이 스프라이트 위에 배경 스프라이트를 적용한 다음, 바늘 스프라이트를 적절한 중심 위치와 각도 방향으로 그 위에 적용합니다. 이제 디스플레이 스프라이트는 메모리에 올바른 이미지 정보를 저장하지만 화면에는 표시되지 않습니다.

 

6. 이제 TFT 화면 위로 디스플레이 스프라이트를 밀어 새로운 그래픽을 확인하세요. "pushSprite" 동작은 매우 빠르게 진행되어 화면에 애니메이션이 적용된 것처럼 보입니다. 바늘은 아래 다이얼 면 위에 제대로 정렬된 것처럼 보입니다.

 

7. 디스플레이를 업데이트하려면 이전 단계를 정기적으로 반복합니다. 이 데모에서는 속도 신호를 0에서 24MPH까지 스위핑한 후 일련의 속도 신호를 따라 바늘을 무작위로 이동합니다.

 

새로운 코드는 다음과 같습니다.

 

// code for Gauge_CrowPanel_Lesson2A.ino

// setup for LCD screen
#include <TFT_eSPI.h> // include graphics library
#define DEG2RAD 0.0174532925
#define COLOR_BORDER TFT_BLUE
#define COLOR_NEEDLE TFT_RED
#define BACKGROUND TFT_DARKGREY

// constants for the display geometry
const int lcdWidth = 240;
const int lcdHeight = 320;
const int gaugeRad = 110;
const int gaugeWidth = 25;
const int gaugeWidth2 = 10;
const int xLoc = lcdWidth / 2;
const int yLoc = lcdHeight / 2;
const int numOffset = 40;

// define TFT objects for display
TFT_eSPI tft = TFT_eSPI(); // Device display
TFT_eSprite display = TFT_eSprite(&tft); //  Sprite for dial face
TFT_eSprite dial = TFT_eSprite(&tft); // Sprite for dial background
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite for needle

// setup constants for the speedometer app
const float minSpeed = 0;
const float maxSpeed = 24;
const float speedRange = maxSpeed - minSpeed;
const String speedLabel = "MPH";

void drawBackground(float speed, String label)
{
  // draw outline and labels onto the dial sprite
  dial.setColorDepth(8);
  dial.createSprite(lcdWidth, lcdHeight);
  dial.setPivot(lcdWidth/2, lcdHeight/2);
  dial.fillScreen(BACKGROUND);
  dial.fillCircle(lcdWidth/2, lcdHeight/2, gaugeRad + 10, COLOR_BORDER);
  dial.fillCircle(lcdWidth/2, lcdHeight/2, gaugeRad, TFT_BLACK);
  dial.fillCircle(lcdWidth/2, lcdHeight/2, 10, TFT_WHITE); // mark dial center
  dial.setTextDatum(MC_DATUM); // locate text graphics by center position
  dial.drawString("MPH",120,240,4); //label gauge MPH

  //draw major and minor tick marks - 3 minor ticks per major mark
    for (float i = 45; i < 316; i+=(3*270/speedRange)) // major marks
    {
      float sx = cos((i - 270) * DEG2RAD);
      float sy = sin((i - 270) * DEG2RAD);
      uint16_t x0 = sx * (gaugeRad - gaugeWidth -1) + xLoc;
      uint16_t y0 = sy * (gaugeRad - gaugeWidth -1) + yLoc;
      uint16_t x1 = sx * (gaugeRad-1) + xLoc;
      uint16_t y1 = sy * (gaugeRad-1) + yLoc;    
      dial.drawWideLine(x0, y0, x1, y1,3,TFT_WHITE); 
    }
    for (float j = 45; j < 315; j+=(270/speedRange)) //minor marks
    {
      float sx = cos((j - 270) * DEG2RAD);
      float sy = sin((j - 270) * DEG2RAD);
      uint16_t x0 = sx * (gaugeRad - gaugeWidth2-1) + xLoc;
      uint16_t y0 = sy * (gaugeRad - gaugeWidth2-1) + yLoc;
      uint16_t x1 = sx * (gaugeRad-1) + xLoc;
      uint16_t y1 = sy * (gaugeRad-1) + yLoc;    
      dial.drawLine(x0, y0, x1, y1, TFT_WHITE);
    }
  //draw number labels
    int dspNum = minSpeed;
    for (float i = 45; i < 316; i+=(3*270/speedRange)) // number labels by major marks
    {
      float sx = cos((i - 270) * DEG2RAD);
      float sy = sin((i - 270) * DEG2RAD);
      uint16_t x0 = sx * (gaugeRad - numOffset -1) + xLoc;
      uint16_t y0 = sy * (gaugeRad - numOffset -1) + yLoc;
      dial.drawNumber(dspNum, x0, y0,2);
      dspNum += 3; 
    }
}

void createNeedle()
{
  needle.setColorDepth(8); // use 8 bit colors to reduce memory rqd
  needle.createSprite(20,120); // make a sprite 20 by 120 pixels
  needle.fillSprite(TFT_TRANSPARENT); //make sprite background transparent
  needle.drawWedgeLine(10,0,10,110,6,2,COLOR_NEEDLE,TFT_TRANSPARENT); // define needle geometry
  needle.setPivot(10,10); // set pivot point at needle axis
}

void updateGauge(float speed)
{
  // calculate needle angle based on speed
  int angle = (speed-minSpeed)*270/(maxSpeed-minSpeed) + 45; // calculate needle angle   
  dial.pushToSprite(&display,0,0,TFT_TRANSPARENT); // add the dial background to display sprite
  needle.pushRotated(&display,angle, TFT_TRANSPARENT); // add the needle to display sprite at correct angle
  display.pushSprite(0,0,TFT_TRANSPARENT); // push the display sprite to the screen (this method reduces flicker)
}

void setup(void) 
{
  Serial.begin(115200);
  delay(200);
  // initialize gauge screen
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  // initialize display sprite
  display.setColorDepth(8);
  display.createSprite(lcdWidth, lcdHeight);
  display.setPivot(lcdWidth/2, lcdHeight/2);
  display.fillSprite(BACKGROUND);
  drawBackground(0,speedLabel); // create dial sprite 
  createNeedle(); // create needle sprite
}

void loop() 
{
  // test program to sweep needle across the dial
  for (float speed = 0; speed <= 24.1; speed +=.2) 
  {
      Serial.println(speed);
      updateGauge(speed);
      //delay(50);
  }
  delay(500);
  // test program to randomly display speeds
  for (int counter = 0; counter < 21; counter += 1)
    {
      long randomspeed = random(24);
      updateGauge(randomspeed);
      delay(500);
    }  
}

 

아래 주요 섹션 중 일부를 살펴보겠습니다. 코드의 처음 두 섹션은 이전과 동일하지만, 배경색을 정의하여 TFT_BLACK에서 TFT_LIGHTGREY 또는 원하는 다른 색상으로 변경할 수 있도록 했습니다. 사용 가능한 다른 기본 색상을 보고 싶으신가요? 여기를 클릭하세요 .

 

// define TFT objects for display
TFT_eSPI tft = TFT_eSPI(); // Device display
TFT_eSprite display = TFT_eSprite(&tft); //  Sprite for dial face
TFT_eSprite dial = TFT_eSprite(&tft); // Sprite for dial background
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite for needle

 

 

위에 설명된 그래픽 요소에 대한 정의 섹션은 다음과 같습니다. TFT 화면과 세 개의 스프라이트가 있으며, 모두 참조(&tft)를 통해 화면에 연결되어 있습니다.

 

drawBackground 코드는 1과에서 설명한 것과 동일합니다. 하지만 이제는 화면에 직접 표시하는 대신 스프라이트 공간에 이미지를 생성하는 데 사용됩니다.

 

void createNeedle()
{
  needle.setColorDepth(8); // use 8 bit colors to reduce memory rqd
  needle.createSprite(20,120); // make a sprite 20 by 120 pixels
  needle.fillSprite(TFT_TRANSPARENT); //make sprite background transparent
  needle.drawWedgeLine(10,0,10,110,6,2,COLOR_NEEDLE,TFT_TRANSPARENT); // define needle geometry
  needle.setPivot(10,10); // set pivot point at needle axis
}

 

 

createNeedle 루틴은 바늘 스프라이트의 크기와 내용을 정의합니다. 바늘 자체는 한쪽 끝의 너비가 6픽셀에서 다른 쪽 끝의 너비가 2픽셀로 점점 가늘어지는 WedgeLine 객체입니다. 바늘은 20x120픽셀 공간에 정의되며, 배치를 위한 피벗 포인트는 첫 번째 끝에서 10픽셀 떨어진 중앙으로 설정됩니다. 간단한 색상만 표시하므로 메모리 사용량을 최소화하기 위해 색상 심도를 8로 설정했습니다.

 

void updateGauge(float speed)
{
  // calculate needle angle based on speed
  int angle = (speed-minSpeed)*270/(maxSpeed-minSpeed) + 45; // calculate needle angle   
  dial.pushToSprite(&display,0,0,TFT_TRANSPARENT); // add the dial background to display sprite
  needle.pushRotated(&display,angle, TFT_TRANSPARENT); // add the needle to display sprite at correct angle
  display.pushSprite(0,0,TFT_TRANSPARENT); // push the display sprite to the screen (this method reduces flicker)
}

 

 

updateGauge 루틴은 디스플레이를 새로운 측정값(속도)으로 업데이트할 때마다 호출됩니다. 위에서 설명한 대로 바늘의 각도 방향(0은 하단 중앙)을 계산합니다. 그런 다음 다이얼 스프라이트를 디스플레이 스프라이트에 직접 밀어 넣고, 회전된 위치에 바늘 스프라이트를 밀어 넣습니다. 이 시점에서 디스플레이 스프라이트는 새 화면의 이미지를 메모리에 저장하지만 화면에는 표시되지 않습니다. 마지막으로 디스플레이 스프라이트를 화면에 밀어 넣고 새 이미지가 표시되는 것을 확인합니다. 이 모든 과정이 동시에 이루어지기 때문에 깜빡임이 최소화됩니다.

 

void setup(void) 
{
  Serial.begin(115200);
  delay(200);
  // initialize gauge screen
  tft.begin();
  tft.setRotation(0);
  // initialize display sprite
  display.setColorDepth(8);
  display.createSprite(lcdWidth, lcdHeight);
  display.setPivot(lcdWidth/2, lcdHeight/2);
  display.fillSprite(BACKGROUND);
  drawBackground(0,speedLabel); // create dial sprite 
  createNeedle(); // create needle sprite
}

 

설정 루틴은 TFT 디스플레이와 디스플레이 스프라이트를 초기화합니다. 또한 바늘을 디스플레이에 놓을 때 사용되는 피벗 포인트를 정의합니다. 마지막으로, drawBackground 루틴을 호출하여 다이얼 면 표시가 포함된 배경 스프라이트를 생성하고 바늘을 생성합니다. 배경 스프라이트는 한 번 생성되어 게이지 업데이트 시 변경되지 않고 사용되므로 매번 다시 계산할 필요가 없습니다.

 

void loop() 
{
  // test program to sweep needle across the dial
  for (float speed = 0; speed <= 24.1; speed +=.2) 
  {
      Serial.println(speed);
      updateGauge(speed);
      //delay(50);
  }
  delay(500);
  // test program to randomly display speeds
  for (int counter = 0; counter < 21; counter += 1)
    {
      long randomspeed = random(24);
      updateGauge(randomspeed);
      delay(500);
    }  
}

 

마지막으로 디스플레이 코드를 테스트하는 루프 루틴이 있습니다. 첫 번째 섹션은 0에서 24까지 0.2MPH 단위로 속도를 순환합니다. 주행 속도와 디스플레이의 부드러움을 확인할 수 있습니다. 두 번째 섹션은 20개의 서로 다른 무작위 값을 0.5초마다 업데이트하여 골프 카트에 설치된 게이지의 모습을 보여줍니다. 실제로 속도계는 속도 사이에서 천천히 움직이며, 매우 무모한 운전자가 아니라면 갑작스럽게 움직이지 않습니다.

 

테스트하려면 위 코드를 복사하여 Gauge_CrowPanel_Lesson2A라는 새 스케치에 붙여넣고, tft_setup.h 파일을 스케치 디렉터리에 복사하세요. 컴파일 후 기기에 다운로드하면 다음과 같은 화면이 표시됩니다.

 

 

 

보시다시피 바늘이 다이얼 위를 부드럽게 움직이지만, 디지털 계기판이 장착된 최신 자동차에서 볼 수 있는 디스플레이만큼 빠르지는 않습니다. Crowpanel 디스플레이는 직렬 연결인 SPI 인터페이스를 통해 ESP32 프로세서에 연결되어 있습니다. 동일한 프로그래밍 기술을 사용하더라도 프로세서와 디스플레이를 병렬로 연결하고 메모리 전송 속도를 높이면 훨씬 빠른 디스플레이를 구현할 수 있습니다. Crowpanel 장치는 빠른 그래픽에 최적화되어 있지는 않지만, 골프 카트 애플리케이션에는 문제없이 작동합니다.

 

수정한 표시 코드

 

마지막으로, 화면 중앙에 속도를 크게 텍스트로 표시하도록 게이지를 수정하겠습니다. 새로운 그래픽을 적용하려면 몇 가지 사소한 변경이 필요합니다.

 

1. 계기판에서 숫자를 제거하세요. 숫자가 적혀 있으면 너무 지저분해 보이고 더 이상 필요하지 않습니다.

 

2. 바늘 포인터가 텍스트와 겹치지 않도록 수정하세요.

 

3. 디스플레이의 흰색 중앙점을 제거합니다. 이 역시 텍스트가 겹치는 것을 방지하기 위한 것입니다.

 

4. updateGauge 함수에서 drawNumber 그래픽 루틴을 사용하여 디스플레이 스프라이트에 숫자 속도(가장 가까운 MPH)를 표시합니다.

 

변경 사항은 여기에서 확인할 수 있습니다. 위의 1번과 3번 항목에 대한 이전 코드를 주석 처리했습니다.

 

// code for Gauge_CrowPanel_Lesson2B.ino

// setup for LCD screen
#include <TFT_eSPI.h> // include graphics library
#define DEG2RAD 0.0174532925
#define COLOR_BORDER TFT_BLUE
#define COLOR_NEEDLE TFT_RED
#define BACKGROUND TFT_DARKGREY

// constants for the display geometry
const int lcdWidth = 240;
const int lcdHeight = 320;
const int gaugeRad = 110;
const int gaugeWidth = 25;
const int gaugeWidth2 = 10;
const int xLoc = lcdWidth / 2;
const int yLoc = lcdHeight / 2;
const int numOffset = 40;

// define TFT objects for display
TFT_eSPI tft = TFT_eSPI(); // Device display
TFT_eSprite display = TFT_eSprite(&tft); //  Sprite for dial face
TFT_eSprite dial = TFT_eSprite(&tft); // Sprite for dial background
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite for needle

// setup constants for the speedometer app
const float minSpeed = 0;
const float maxSpeed = 24;
const float speedRange = maxSpeed - minSpeed;
const String speedLabel = "MPH";

void drawBackground(float speed, String label)
{
  // draw outline and labels onto the dial sprite
  dial.setColorDepth(8);
  dial.createSprite(lcdWidth, lcdHeight);
  dial.setPivot(lcdWidth/2, lcdHeight/2);
  dial.fillScreen(BACKGROUND);
  dial.fillCircle(lcdWidth/2, lcdHeight/2, gaugeRad + 10, COLOR_BORDER);
  dial.fillCircle(lcdWidth/2, lcdHeight/2, gaugeRad, TFT_BLACK);
  //dial.fillCircle(lcdWidth/2, lcdHeight/2, 10, TFT_WHITE); // mark dial center
  dial.setTextDatum(MC_DATUM); // locate text graphics by center position
  dial.drawString("MPH",120,240,4); //label gauge MPH

  //draw major and minor tick marks - 3 minor ticks per major mark
    for (float i = 45; i < 316; i+=(3*270/speedRange)) // major marks
    {
      float sx = cos((i - 270) * DEG2RAD);
      float sy = sin((i - 270) * DEG2RAD);
      uint16_t x0 = sx * (gaugeRad - gaugeWidth -1) + xLoc;
      uint16_t y0 = sy * (gaugeRad - gaugeWidth -1) + yLoc;
      uint16_t x1 = sx * (gaugeRad-1) + xLoc;
      uint16_t y1 = sy * (gaugeRad-1) + yLoc;    
      dial.drawWideLine(x0, y0, x1, y1,3,TFT_WHITE); 
    }
    for (float j = 45; j < 315; j+=(270/speedRange)) //minor marks
    {
      float sx = cos((j - 270) * DEG2RAD);
      float sy = sin((j - 270) * DEG2RAD);
      uint16_t x0 = sx * (gaugeRad - gaugeWidth2-1) + xLoc;
      uint16_t y0 = sy * (gaugeRad - gaugeWidth2-1) + yLoc;
      uint16_t x1 = sx * (gaugeRad-1) + xLoc;
      uint16_t y1 = sy * (gaugeRad-1) + yLoc;    
      dial.drawLine(x0, y0, x1, y1, TFT_WHITE);
    }
  //draw number labels
    // int dspNum = minSpeed;
    // for (float i = 45; i < 316; i+=(3*270/speedRange)) // number labels by major marks
    // {
    //   float sx = cos((i - 270) * DEG2RAD);
    //   float sy = sin((i - 270) * DEG2RAD);
    //   uint16_t x0 = sx * (gaugeRad - numOffset -1) + xLoc;
    //   uint16_t y0 = sy * (gaugeRad - numOffset -1) + yLoc;
    //   dial.drawNumber(dspNum, x0, y0,2);
    //   dspNum += 3; 
    // }
}

void createNeedle()
{
  needle.setColorDepth(8); // use 8 bit colors to reduce memory rqd
  needle.createSprite(20,120); // make a sprite 20 by 120 pixels
  needle.fillSprite(TFT_TRANSPARENT); //make sprite background transparent
  //needle.drawWedgeLine(10,0,10,110,6,2,COLOR_NEEDLE,TFT_TRANSPARENT); // define needle geometry
  needle.drawWedgeLine(10,70,10,110,8,4,COLOR_NEEDLE,TFT_TRANSPARENT);  // new needle geometry
  needle.setPivot(10,10); // set pivot point at needle axis
}

void updateGauge(float speed)
{
  // calculate needle angle based on speed
  int angle = (speed-minSpeed)*270/(maxSpeed-minSpeed) + 45; // calculate needle angle   
  dial.pushToSprite(&display,0,0,TFT_TRANSPARENT); // add the dial background to display sprite 
  display.setTextDatum(MC_DATUM); // place text based on middle center origin
  display.drawNumber(speed, 120, 160, 8); // insert the speed text
  needle.pushRotated(&display,angle, TFT_TRANSPARENT); // add the needle to display sprite at correct angle
  display.pushSprite(0,0,TFT_TRANSPARENT); // push the display sprite to the screen (this method reduces flicker) 
}

void setup(void) 
{
  Serial.begin(115200);
  delay(200);
  // initialize gauge screen
  tft.begin();
  tft.setRotation(0);
  // initialize display sprite
  display.setColorDepth(8);
  display.createSprite(lcdWidth, lcdHeight);
  display.setPivot(lcdWidth/2, lcdHeight/2);
  display.fillSprite(BACKGROUND);
  drawBackground(0,speedLabel); // create dial sprite 
  createNeedle(); // create needle sprite
}

void loop() 
{
  // test program to sweep needle across the dial
  for (float speed = 0; speed <= 24.1; speed +=.2) 
  {
      Serial.println(speed);
      updateGauge(speed);
      //delay(50);
  }
  delay(500);
  // test program to randomly display speeds
  for (int counter = 0; counter < 21; counter += 1)
    {
      long randomspeed = random(24);
      updateGauge(randomspeed);
      delay(500);
    }  
}

 

이제 컴파일하여 장치에 다운로드하면 화면이 이렇게 보일 것입니다.

 

 

 

 

이제 스프라이트를 사용하여 사실적인 게이지를 만드는 방법을 이해하셨기를 바랍니다. 물론, 필요에 따라 그래픽 기능의 크기, 글꼴, 색상, 배치를 수정할 수도 있습니다.

 

다음 레슨에서는 휠 모션을 측정하는 홀 효과 센서를 부착하고, 속도, 거리, 런타임 값을 계산하는 코드를 추가해 보겠습니다. 이 내용이 흥미롭고 유익하기를 바랍니다.

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩