이번 프로젝트에서는 ADC에 대해서 알아보도록 하겠습니다.
ADC란?
ADC는 아날로그 신호를 디지털 형식으로 변환하는 장치입니다. 마이크로컨트롤러 내의 ADC는 센서와 같은 외부 장치로부터 아날로그 신호를 받아 이를 마이크로컨트롤러가 처리할 수 있는 디지털 값으로 변환합니다.
STM32F411RETx의 ADC란
- STM32F411 시리즈는 일반적으로 여러 채널의 ADC를 갖추고 있으며, 이를 통해 다양한 아날로그 입력을 동시에 처리할 수 있습니다.
- 각 ADC 핀은 특정 아날로그 신호를 받아들이고, 이를 내부적으로 디지털 값으로 변환하여 프로세서가 사용할 수 있도록 합니다.
- 변환된 값은 센서 데이터 처리, 전압 측정, 사용자 입력 인식 등 다양한 애플리케이션에 사용됩니다.
X축과 Y축은 ADC 기능으로 구현하고 Z축은 버튼을 누르는 형식이므로 GPIO의 Input 모드로 구현하였습니다.
ADC 설정
- Mode: 독립 모드로 설정되어 있어, 다른 ADC와 독립적으로 동작합니다.
- Clock Prescaler: PCLK2 (주변 장치 클록 2)를 4로 나누어 ADC의 클록 속도를 조정합니다. 이는 ADC가 데이터를 변환하는 속도를 결정합니다.
- Resolution: 12비트 해상도, 이는 ADC가 변환할 수 있는 데이터의 정밀도를 의미하며, 12비트는 최대 4096(2^12) 단계의 아날로그 값을 디지털 값으로 변환할 수 있습니다.
- Data Alignment: 데이터 정렬은 오른쪽으로 설정되어 있어, 변환된 데이터의 하위 비트가 결과 레지스터의 오른쪽에 위치합니다.
- Scan Conversion Mode: 활성화되어 있으며, 이는 여러 채널에서의 연속적인 데이터 변환을 가능하게 합니다.
- Continuous Conversion Mode: 비활성화되어 있으며, 이는 단일 변환 후에 멈춤을 의미합니다.
- Discontinuous Conversion Mode: 활성화되어 있으며, 이는 일정 개수의 변환 후에 일시적으로 변환을 멈출 수 있음을 의미합니다.
- Number Of Discontinuous Conversion: '1'로 설정되어 있어, 하나의 변환 후 일시 중지됩니다.
ADC 변환 모드 설정
- End Of Conversion Selection: 변환의 끝을 나타내는 EOC 플래그는 단일 채널 변환 종료 시 설정됩니다.
ADC 정규 변환 모드
- Number Of Conversion: 2개의 변환을 수행합니다.
- External Trigger Conversion Source: 소프트웨어에 의해 시작되는 정규 변환.
- External Trigger Conversion Edge: 외부 트리거는 사용되지 않습니다.
- Rank and Channel: 채널 10과 11에 대해 각각 1랭크와 2랭크가 지정되어 있으며, 각각 15 사이클의 샘플링 시간을 가집니다.
Generation 후 main.c에서 코드를 작성해줍니다.
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int xa[100],ya[100];
void MakeItem()
{
int i;
srand(htim3.Instance->CNT);
for(i=0;i<100;i++)
{
int v1 = rand(); //0 ~ 2,147,483,647
int v2 = rand();
//printf("v : %d\r\n", x);
//HAL_Delay(100);
xa[i] = (((double)v1 / 2147483647.)) * 80;
ya[i] = (((double)v2 / 2147483647.)) * 24;
//printf("[%d;%d](%d,%d)\r\n",v1, v2, xa[i], ya[i]);
printf("\033[%d;%dHo",ya[i], xa[i]);
}
printf("\n");
}
/* USER CODE END 0 */
이 코드는 MakeItem 함수를 통해 화면에 여러 위치에 'o' 문자를 무작위로 출력하는 기능을 수행합니다. 게임에서 아이템이나 캐릭터의 초기 무작위 배치에 유용할 수 있으며, 시각적 테스트나 사용자 인터페이스의 동적 요소를 표시하는 데 사용될 수 있습니다.
난수 초기화
- srand(htim3.Instance->CNT);: srand 함수는 난수 생성기의 시드를 설정합니다. 여기서는 TIM3의 현재 카운터 값을 사용하여 매번 다른 시드를 제공함으로써 난수의 무작위성을 보장합니다.
난수 생성 및 위치 계산
- 루프는 0부터 99까지 실행되며, 각 반복마다 rand() 함수를 두 번 호출하여 v1과 v2를 생성합니다. 이 값들은 0에서 2,147,483,647 범위의 정수입니다.
- xa[i]와 ya[i] 배열에는 각각 x축과 y축 좌표를 저장합니다. 이 좌표는 화면 크기에 맞추어 조정됩니다 (80x24). v1과 v2를 최대 난수 값으로 나누어 정규화한 뒤, 화면 크기에 맞게 스케일링합니다.
화면에 문자 출력
- printf("\033[%d;%dHo", ya[i], xa[i]);: 이 코드는 ANSI 이스케이프 코드를 사용하여 터미널에서 커서를 ya[i], xa[i] 위치로 이동한 다음, 해당 위치에 'o' 문자를 출력합니다. 여기서 \033[는 이스케이프 시퀀스를 시작하며, %d;%dH는 커서를 특정 행과 열로 이동시킵니다.
함수 종료
- printf("\n");: 마지막에 줄바꿈 문자를 출력하여 커서를 다음 줄로 이동시킵니다.
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_ADC1_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim3);
ProgramStart("ADC Polling");
printf("\033[2J\033[?25l\n"); // Clear screen & cursor invisible
MakeItem();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
int sx=80,sy=24; // screen size
int cx=39,cy=12; // initial position
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000); // start Trigger
int v = ((double)(HAL_ADC_GetValue(&hadc1) / 1400)) - 1;
int x = cx + v;
x = (x > 78) ? 78 : (x < 0) ? 0 : x;
//HAL_ADC_Stop(&hadc1); // skip
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000); // start Trigger
v = ((double)(HAL_ADC_GetValue(&hadc1) / 1400)) - 1;
int y = cy + v;
y = (y > 23) ? 23 : (y < 0) ? 0 : y;
//HAL_ADC_Start(&hadc1);
//HAL_ADC_PollForConversion(&hadc1, 1000); // start Trigger
int z = HAL_GPIO_ReadPin(Z_Axis_GPIO_Port, Z_Axis_Pin);
printf("\033[0;0HADC Value : (%d,%d,%d)", x, y, z); // location info
printf("\033[%d;%dH \033[%d;%dH@\033[A\n", cy, cx, y, x);
cx = x; cy = y; //save current pos
HAL_Delay(100);
}
/* USER CODE END 3 */
}
STM32 마이크로컨트롤러를 이용하여 ADC 값을 읽고, 터미널 스크린상에 캐릭터 '@'를 움직이게 하는 프로그램입니다. 주요 기능은 ADC 값을 통해 캐릭터의 위치를 업데이트하고, 그 위치를 터미널에 시각적으로 표시하도록 합니다.
초기화 및 설정
- HAL_Init(): 하드웨어 추상화 레이어(HAL) 라이브러리의 모든 모듈을 초기화합니다.
- SystemClock_Config(): 시스템 클록을 설정합니다.
- MX_GPIO_Init(), MX_USART2_UART_Init(), MX_ADC1_Init(), MX_TIM3_Init(): GPIO, UART, ADC, TIM3를 초기화하는 함수를 호출합니다.
- HAL_TIM_Base_Start(&htim3): 타이머 3을 시작하여 프로그램에서 사용할 준비를 합니다.
핀 설정 및 게임 초기화
- ProgramStart("ADC Polling"): 프로그램 시작을 알리는 메시지를 출력하고 사용자가 시작하길 기다립니다.
- MakeItem(): 화면에 무작위 위치에 'o' 문자를 출력하는 함수를 호출하여 초기 아이템(또는 장애물)을 생성합니다.
주 반복 루프
- ADC 값을 읽어 캐릭터의 위치를 업데이트합니다. HAL_ADC_Start()와 HAL_ADC_PollForConversion()을 사용하여 ADC 변환을 시작하고, 완료될 때까지 기다립니다.
- HAL_ADC_GetValue(&hadc1): ADC에서 읽은 값을 반환합니다. 이 값을 스크린 크기에 맞게 조정하여 캐릭터의 새 위치를 계산합니다.
- 캐릭터의 x, y 위치를 조정합니다. 스크린의 경계를 넘지 않도록 조건을 걸어 위치를 조정합니다.
- HAL_GPIO_ReadPin(Z_Axis_GPIO_Port, Z_Axis_Pin): Z 축 핀의 상태를 읽어 'z' 값을 얻습니다. 이 값은 추가 입력 또는 기능을 위해 사용될 수 있습니다.
- ANSI 이스케이프 코드를 사용하여 터미널에 위치 정보와 캐릭터를 출력합니다. 이스케이프 코드는 커서를 원하는 위치로 이동하고, 텍스트를 출력하는 데 사용됩니다.
디스플레이 갱신
- printf 함수를 사용하여 ADC 값과 캐릭터의 위치를 터미널에 출력합니다. 이 때, 이전에 캐릭터가 있던 위치를 지우고 새 위치에 캐릭터를 그립니다.
딜레이
- HAL_Delay(100): 100밀리초 동안 대기하여, 캐릭터의 움직임 속도를 제어합니다.
종합적으로 ADC 값에 따라 캐릭터를 스크린 상에서 움직이게 하여, 조이스틱의 아날로그 출력을 디지털 위치 정보로 변환하는 방법을 시각적으로 보여주는 코드입니다.
Run을 실행하고 보드에서 테스트를 해보도록 하겠습니다.
'하만 세미콘 아카데미 > Embedded (STM32)' 카테고리의 다른 글
[Embedded] STM32 ADC - Interrupt (0) | 2024.05.31 |
---|---|
[Embedded] STM32 ADC - DMA (0) | 2024.05.31 |
[Embedded] STM32 - US PWM (0) | 2024.05.31 |
[Embedded] STM32 - US Interrupt (0) | 2024.05.31 |
[Embedded] STM32 - US GPIO (0) | 2024.05.31 |