
자이로스코프 : 축 중심의 회전운동
가속도 : 축 방향으로 직선운동
가속도계, 자이로스코프 회로가 다 구현되어 있는 시스템
MEMS(micro electro mechinaical system) 물체의 공간이동 변위를 측정하는 회로로 구현
축당 변위값 ADC -> 디지털 신호로
자이로는 코리올리 가속으로 인한 공명하는 질량과 틀간의 변위를 측정한다. ( 회전하는 좌표계에서 직선 운동을 할 때, 운동 방향과 수직으로 생기는 가상의 가속도 )
MEM 내에는 진동하는 미세 질량이 있다. 센서가 회전하면 코리올리 힘 생김. 이걸로 계산
F= ma, 물체의 가속도는 힘으로부터 역추적한다.
가속도 센서

Sensor input에는 측정 범위 (+-3g ~ +-3.6g 즉 중력의 3배에서 3.6배까지 측정 가능) , 비선형성(센서 출력이 이론적인 직선에서 얼마나 벗어났는지 ), 센서 패키지가 기준 축에 대해 얼마나 틀어질 수 있는지, 축간에 서로 직각이 아닐 오차, 한 축에 가속도가 다른 축에 얼마나 영향을 미치는지.. 등이 표기되어 있다.
감도에는 1g에 따라 출력 전압이 얼마나 변하는지, 온도에 따라 얼마나 변하는지가 나와있다.
Zero g bias level 즉 가속도가 아예 없을때 기본적으로 나오는 전압 등이 표기되 있다.


마찬가지로 센서의 측정 범위, 비선형성, 영점 오프셋 ( 회전하지 않을 때 가지는 오차 ) 등이 나와있다.
초음파 센서, IR센서

Echo 신호는 GPIO AF의 TIM3에 연결되어 있음 -> Input Capture 모드를 설정하여 인터럽트 핸들러에서 장애물까지의 거리를 계산한 USS_TRIG 신호로 3방향의 초음파 동시에 송출
회로도를 참고하여 보면 GPIOF 포트의 핀 6번 즉 PF6에 연결되어 있다.
TIM10은 16비트 카운터를 이용하는 범용 타이머
3개의 채널로부터 각각 초음파 센서 3개의 echo 신호 입력을 읽음.
capture : 외부 신호가 들어온 순간, 타이머 카운터의 값을 저장
CCR : cpature compare register
3,5 의 CCRx 차를 이용하여 Count값 계산

주황색 신호는 외부에서 들어온 펄스 신호
T1,T2는 상승 엣지 -> 타이머는 해당 시점의 카운터 값을 캡쳐한다.
T2-T1 하여 T3 값을 얻음
타이머는 count 단위로 시간을 기록 즉 Timer 입럭 클럭 속도로 나눠줘야 함.
타이머 클럭은 1MHz, 1초에 1,000,000번 카운트
카운트 차 5000
펄스 폭 = 5000 / 1,000,000 = 0.005초
// TIM_HandleTypeDef 구조체 선언 및 기본 설정
TimHandle1.Instance = TIM3; // 사용할 타이머: TIM3
// 카운트 최대값 설정 (0xFFFF = 65535)
TimHandle1.Init.Period = 0xFFFF; // 타이머가 0부터 65535까지 센다
// 프리스케일러(분주비) 설정: 타이머 입력 클럭을 나누는 값
TimHandle1.Init.Prescaler = uwPrescalerValue; // 원하는 분주비 값 사용
// 클럭 분주 설정 (일반적으로 0 또는 1)
TimHandle1.Init.ClockDivision = 0; // 클럭 분주 사용 안함
// 카운터 모드 설정 (업카운트: 0→65535로 증가)
TimHandle1.Init.CounterMode = TIM_COUNTERMODE_UP;
// 타이머 초기화 (Input Capture 모드로 초기화)
HAL_TIM_IC_Init(&TimHandle1);
// Input Capture 설정 구조체 선언 및 값 지정
sICConfig1.ICPolarity = TIM_ICPOLARITY_RISING; // 상승 에지에서 캡처
sICConfig1.ICSelection = TIM_ICSELECTION_DIRECTTI; // 직접 입력 캡처 사용
sICConfig1.ICPrescaler = TIM_ICPSC_DIV1; // 프리스케일러 없음, 모든 신호에서 캡처
sICConfig1.ICFilter = 0; // 입력 필터 사용 안함
// uBrain의 초음파는 TIM3의 2, 3, 4번 채널을 사용
HAL_TIM_IC_ConfigChannel(&TimHandle1, &sICConfig1, TIM_CHANNEL_2); // 2번 채널 설정
HAL_TIM_IC_Start_IT(&TimHandle1, TIM_CHANNEL_2); // 2번 채널 캡처 인터럽트 시작
HAL_TIM_IC_ConfigChannel(&TimHandle1, &sICConfig1, TIM_CHANNEL_3); // 3번 채널 설정
HAL_TIM_IC_Start_IT(&TimHandle1, TIM_CHANNEL_3); // 3번 채널 캡처 인터럽트 시작
HAL_TIM_IC_ConfigChannel(&TimHandle1, &sICConfig1, TIM_CHANNEL_4); // 4번 채널 설정
HAL_TIM_IC_Start_IT(&TimHandle1, TIM_CHANNEL_4); // 4번 채널 캡처 인터럽트 시작
Duty Cycle : 한 주기에서 신호가 High 상태로 유지되는 시간의 비율
듀티비 : High 시간 / 전체 시간 * 100
PWM은 첫번째 그래프처럼 듀티비를 조절하여 속도나 LED 밝기 등을 제어할 수 있다.
PWM모드는
TIMx_ARR ( auto reload register ) 을 이용하여 출력신호의 주기를 결정.
TIMx_CCRx (Capture/Compare register ) 을 이용하여 듀티비 결정
ARR = 5000 이고, CCR = 2500 이면 듀티비는 50%
1초에 2회 초음파 생성을 위하여, ARR = 0xFFFF로 설정하고 CCR = 2 설정했을 때 10us 이상의 듀티를 보장할 수 있도록 Prescalar 값을 설정하는 것이 핵심
1초에 2회 초음파 생성 -> 2Hz로 거리 측정
트리거 펄스의 길이는 10us 이상으로 생성. 펄스의 길이는 0.5초이다. 해당 주기를 타이머가 카운트하여 반복적으로 펄스를 내보낸다.
Infrared laser 센서
초음파 센서와 비슷
발광부에서 적외선 방출, 수광부에서 적외선 감지.
적외선의 양! 에 따라 변하는 전류의 양을 ADC를 통해서 계산한다.
마찬가지로 3개의 IR센서는 GPIO Port C에 3개의 Analog 입력으로 설정하여 사용한다.
그러니까 모터를 제어할 때 어떤 신호선이 모터 드라이버와 MCU간의 신호 연결을 하는 지
모터 제어할 때 어떤 신호선이 STM32의 어느 핀에 연결되는지 표시
모터 드라이버의 입력 신호 1A 2A 1B 2B EN
모터 속도 측정 신호 TACHO 는
STM32의 특정 핀들에 각각 연결되어 있어서 MCU는 해당 핀들을 read/write하여 묘터를 컨트롤할 수 있음.
모터 인터페이스는 PWM을 + - 방향으로 발생시키는 2개의 출력핀이 TIM3,4에 연결되어 있음
타이머에서 제공하는 Encoder Interface를 이용하여 현재 바퀴의 회전방향과 회전 수를 알아낼 수 있다.
왼쪽/오른쪽 두개의 모터 구동과 Encoder1,2 인터페이스가 연결되어 있음
EXTI External Interrupt, 외부 인터럽트
GPIO포트들의 각 핀들은 그룹으로 묶어서 EXTI0 ~ EXTI15 까지 총 16개의 외부 인터럽트를 발생시킬 수 있다.
uBrain에서는 2개의 인코더가 4핀에 연결되어 있으므로 EXTI 4 5 3 15를 발생시키도록 설정
천천히 해보자. 인코더는 모터 축이 얼마나 어떤 방향으로 알려주는 센서이다. 대부분 2개의 신호선을 사용하고 AB 또는 TI1 TI2 라고 부른다. 한개는 얼마나 돌았는지만 알 수 있고, 두개가 있어야 어느 방향으로 도는지 알 수 있다.
두 신호가 0과 1로 바뀌는데 TI1이 상승할 때 TI2가 0이면 앞으로
TI1가 상승할 때 TI2가 1이면 뒤이다.
따라서 현재 채널의 카운터 값을 읽거나 인터럽트 발생 횟수를 저장해 놓으면 현재 모터의 회전방향과 회전속도를 알아 낼 수 있다.
// EXTI3 인터럽트가 발생하면 벡터테이블에서 이 함수로 점프함
void EXTI3_IRQHandler(void)
{
// HAL 라이브러리의 공통 EXTI 인터럽트 핸들러 호출 (핀 번호 3)
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
}
// HAL 라이브러리의 EXTI 인터럽트 핸들러
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
// EXTI 인터럽트가 실제로 발생했는지 확인
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
// 인터럽트 플래그를 클리어(0으로) 해줌 (다음 인터럽트 처리를 위해)
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
// 사용자 정의 콜백 함수 호출 (main.c에서 구현)
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
// 사용자 정의 콜백 함수 (main.c에서 구현)
// 실제로 핀 변화에 따라 동작을 코딩하는 부분
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case GPIO_PIN_15: // PA15, Encoder 2의 A=1 상태에서 인터럽트 발생
encoder_right = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3); // Encoder 2의 B값 읽기
if(encoder_right == 0) // B=0이면 Forward(정방향) 회전 중
{
motorInterrupt1++; // 카운터 증가 (정방향)
encoder_right = READY; // 상태 플래그 갱신
}
else if(encoder_right == 1) // B=1이면 Backward(역방향) 회전 중
{
motorInterrupt1--; // 카운터 감소 (역방향)
encoder_right = READY; // 상태 플래그 갱신
}
break;
// (다른 핀에 대한 처리도 추가 가능)
}
}
case GPIO_PIN_4: // Encoder 1 PB4에서 인터럽트 발생 시
// Encoder 1의 B 신호(PB5)를 읽어서 방향 판단
encoder_left = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5);
if(encoder_left == 0) // B신호가 0이면 정방향(Forward) 회전
{
motorInterrupt2++; // 카운터 증가 (정방향)
encoder_left = READY; // 상태 플래그 갱신
}
else if(encoder_left == 1) // B신호가 1이면 역방향(Backward) 회전
{
motorInterrupt2--; // 카운터 감소 (역방향)
encoder_left = READY; // 상태 플래그 갱신
}
break;
예를 들어 모터가 한 바퀴 돌 때 인코더가 100개의 펄스 발생시키면
모터가 1바퀴 -> 카운터가 100만큼 증가 또는 감소
근데 카운터 변화량이 이동거리로 바로 가진 않음.
모터가 한바퀴 회전 -> 인코더 신호로부터 계산된 카운터의 변화량에 따른 이동거리는 모터의 조립상태나 제적한 모터의 상태에 따라 다름. 인코더 값을 먼저 실험을 통해 측정한 후 자신의 로봇에 맞도록 코드를 조정해야 한다.
이러한 과정을 Cailbration이라고 부름.
기계바다, 베터리 충전 상황, 날씨, 장소 등에 따라 반드시 Cailbration이 필요하다.
주행 전에 제어를 위한 세팅값을 측정을 통해 결정한다. (주행시간과 주행거리의 관계, 회전과 회전시간의 관계 )
parameter
전진거리/전진시간
좌회전각도/좌회전시간
우회전각도/우회전시간
모터 인코더 값 오차

uBrain에서는 2개의 모터를 사용 -> TIM3 TIM4 에 대해 동일한 PWM 모드 설정
모터의 전원은 PB2 핀에 연결되어 있음. 1로 세팅하여 전원 공급
TIM3 4 두개의 채널을 두 방향의 PWM 출력으로 설정
예제에서는 20000개의 펄스를 최대 출력으로 하는 듀티비를 제어한다.
Duty ratio = period / pulse * 100
펄스의 조정 : 펄스길이 재설정 - 재적용 - HAL_TIM_start 순으로 설정
두 개의 모터 -> TIM3 TIM4 타이머의 PWM 신호로 제어, PB2 핀을 1로 올려 모터 전원을 킨다.
PWM의 Period와 Pulse 구간을 조정하여 듀티비 즉 모터 속도를 바꿀 수 있다.
// ===============================
// 인코더용 타이머 설정 코드 예시
// ===============================
// 1. TIM_Encoder_InitTypeDef 구조체 선언 및 초기화
TIM_Encoder_InitTypeDef sENConfig;
// 인코더 모드 설정: TI1과 TI2(두 신호선)를 모두 사용 (쿼드러처 인코더 모드)
sENConfig.EncoderMode = TIM_ENCODERMODE_TI12;
// 채널1, 채널2 모두 직접 입력으로 선택 (A, B 신호를 직접 받음)
sENConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sENConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
// 채널1, 채널2 모두 상승 에지에서 신호 감지
sENConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sENConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
// 입력 필터 사용 안 함 (노이즈가 심하면 값 조정 가능)
sENConfig.IC1Filter = 0;
sENConfig.IC2Filter = 0;
// 프리스케일러 사용 안 함 (모든 신호 변화에 반응)
sENConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sENConfig.IC2Prescaler = TIM_ICPSC_DIV1;
// 2. TIM_HandleTypeDef 구조체 선언 및 초기화
TIM_HandleTypeDef TimHandle3;
// 사용할 타이머 인스턴스 지정 (여기서는 TIM5)
TimHandle3.Instance = TIM5;
// 타이머의 주기(최대 카운트 값) 설정 (여기서는 1000)
TimHandle3.Init.Period = 1000;
// 타이머 프리스케일러 값 설정 (클럭을 얼마나 나눌지, uwPrescalerValue는 미리 계산된 값)
TimHandle3.Init.Prescaler = uwPrescalerValue;
// 클럭 분주 사용 안 함
TimHandle3.Init.ClockDivision = 0;
// 카운터가 증가(UP)하는 모드로 설정
TimHandle3.Init.CounterMode = TIM_COUNTERMODE_UP;
// 3. 인코더 모드로 타이머 초기화
HAL_TIM_Encoder_Init(&TimHandle3, &sENConfig);
// 4. 인코더 관련 타이머 채널 시작 (채널 1, 2 모두)
HAL_TIM_Encoder_Start(&TimHandle3, TIM_CHANNEL_1);
HAL_TIM_Encoder_Start(&TimHandle3, TIM_CHANNEL_2);
// ===============================
// 설명
// ===============================
// - 이 코드는 TIM5의 채널 1, 2를 인코더 입력(A, B 신호)으로 사용하도록 설정합니다.
// - 인코더 모드(TI12)를 사용하면, 두 신호의 위상 차이를 자동으로 분석해
// 모터가 어느 방향(정방향/역방향)으로 얼마나 회전했는지 타이머 카운터 값으로 알 수 있습니다.
// - 카운터 값이 증가(UP)하면 forward(정방향) 회전, 감소(Down)하면 backward(역방향) 회전을 의미합니다.
// - 실제로는 타이머 카운터 값을 읽어서 모터의 위치, 이동거리, 회전 방향 등을 소프트웨어에서 계산할 수 있습니다.
// - 좌측/우측 모터가 있다면, 타이머4도 동일하게 설정해서 각각의 인코더 신호를 읽을 수 있습니다.
// 모터를 전진(Forward)시키는 함수
void Motor_Forward(void)
{
// 왼쪽 모터: TIMHandle1의 채널 1번 PWM 시작 (정방향)
HAL_TIM_PWM_Start(&TimHandle1, TIM_CHANNEL_1);
// 오른쪽 모터: TIMHandle2의 채널 2번 PWM 시작 (정방향)
HAL_TIM_PWM_Start(&TimHandle2, TIM_CHANNEL_2);
}
// 모터를 후진(Backward)시키는 함수
void Motor_Backward(void)
{
// 왼쪽 모터: TIMHandle1의 채널 2번 PWM 시작 (역방향)
HAL_TIM_PWM_Start(&TimHandle1, TIM_CHANNEL_2);
// 오른쪽 모터: TIMHandle2의 채널 1번 PWM 시작 (역방향)
HAL_TIM_PWM_Start(&TimHandle2, TIM_CHANNEL_1);
}
// 모터를 좌회전(Left)시키는 함수
void Motor_Left(void)
{
// 왼쪽 모터: TIMHandle1의 채널 2번 PWM 시작 (역방향)
HAL_TIM_PWM_Start(&TimHandle1, TIM_CHANNEL_2);
// 오른쪽 모터: TIMHandle2의 채널 2번 PWM 시작 (정방향)
HAL_TIM_PWM_Start(&TimHandle2, TIM_CHANNEL_2);
// → 왼쪽 바퀴는 뒤로, 오른쪽 바퀴는 앞으로 돌려서 제자리에서 좌회전
}
// 모터를 우회전(Right)시키는 함수
void Motor_Right(void)
{
// 왼쪽 모터: TIMHandle1의 채널 1번 PWM 시작 (정방향)
HAL_TIM_PWM_Start(&TimHandle1, TIM_CHANNEL_1);
// 오른쪽 모터: TIMHandle2의 채널 1번 PWM 시작 (역방향)
HAL_TIM_PWM_Start(&TimHandle2, TIM_CHANNEL_1);
// → 왼쪽 바퀴는 앞으로, 오른쪽 바퀴는 뒤로 돌려서 제자리에서 우회전
}