[STM32#18] HAL_UART_Receive_IT, HAL_UART_RxCpltCallback수신 인터럽트를 이용해서 각종 모터 제어해보기!(녹칸다 내맘대로 STM32)
프로그래밍/STM32 2025. 12. 10. 16:42
https://youtube.com/live/NtAF5P2SCoM
[STM32#18] HAL_UART_Receive_IT, HAL_UART_RxCpltCallback수신 인터럽트를 이용해서 각종 모터 제어해보기!(녹칸다 내맘대로 STM32)
심심한녹칸다의 내맘대로 STM32시리즈이다!
STM32시리즈의 모든 자료는 구글 슬라이드에 작성하고 모두에게 공유되어있음!
https://docs.google.com/presentation/d/1myA5iYbjuKsLWLqtRLKAiRfwUwvqB1d1RGjiMIIgp3I/edit?slide=id.g3ae770b99e6_1_256#slide=id.g3ae770b99e6_1_256
녹칸다의 STM32쉴드에 붙어있는 각종 모터를 제어해보는 것입니다!
수신인터럽트 ioc 설정

1.STM32의 usart2수신부분을 폴링방식에서 인터럽트 방식으로 변환한 기본 예제를 구현하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
char buff2[100];
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
//1초간격으로 stm32의 타이머카운터값을 전송한다!
sprintf(buff2,"now getTick = %d\n",HAL_GetTick());
HAL_UART_Transmit(&huart2, buff2, strlen(buff2), 100);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\r'){
buff[pos] = '\n';
pos++;
buff[pos] = '\0';
//echo
HAL_UART_Transmit(&huart2, buff, strlen(buff), 100);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
/* USER CODE END 4 */
2.C#윈폼에서 STM32와 시리얼통신으로 연결되고 버튼1을 누르면 0도, 버튼2를 누르면 90도, 버튼3을 누르면 180도로 서보모터의 각도가 회전되도록 하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
void set_servo(uint8_t degree){
//degree는 0~180도 범위이고 CCR4는 600부터 2400의 범위이다!
TIM2->CCR4 = (uint32_t)(600+(2400-600)*degree/180.0);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); //PWM시작~
char buff2[100];
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
//"0" ~ "180"
int degree = atoi(buff);
set_servo(degree);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("0");
}
}
private void button3_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("90");
}
}
private void button4_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("180");
}
}
3.이번에는 컨트롤중에 numericupdown이라는 녀석을 이용해서 사용자가 입력한 숫자값을 버튼을 누르면 STM32에 전송해서 각도를 제어하시오!(STM32코드는 예제2와 동일함)

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
void set_servo(uint8_t degree){
//degree는 0~180도 범위이고 CCR4는 600부터 2400의 범위이다!
TIM2->CCR4 = (uint32_t)(600+(2400-600)*degree/180.0);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); //PWM시작~
char buff2[100];
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
//"0" ~ "180"
int degree = atoi(buff);
set_servo(degree);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void button5_Click(object sender, EventArgs e)
{
//전송버튼 클릭이벤트!
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(numericUpDown1.Value.ToString());
}
}
4.이번에는 컨트롤중에 hcrollbar라는 녀석을 이용해서 컨트롤을 드래그해서 서보모터의 각도를 회전시키시오!(STM32코드는 예제2와 동일함)

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
void set_servo(uint8_t degree){
//degree는 0~180도 범위이고 CCR4는 600부터 2400의 범위이다!
TIM2->CCR4 = (uint32_t)(600+(2400-600)*degree/180.0);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); //PWM시작~
char buff2[100];
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
//"0" ~ "180"
int degree = atoi(buff);
set_servo(degree);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
label2.Text = hScrollBar1.Value.ToString();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(hScrollBar1.Value.ToString());
}
}
5.예제4에서 STM32보드가 100밀리초 간격으로 현재 서보모터의 설정 각도를 전송하고 C#은 그 값을 화면에 출력하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
int degree = 0; //현재 서보모터의 각도값
void set_servo(uint8_t degree){
//degree는 0~180도 범위이고 CCR4는 600부터 2400의 범위이다!
TIM2->CCR4 = (uint32_t)(600+(2400-600)*degree/180.0);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4); //PWM시작~
char buff2[100];
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
//0.1초간격으로 degree값을 C#으로 전송한다!(종료문자 \n)
sprintf(buff2,"%d\n",degree);
HAL_UART_Transmit(&huart2, buff2, strlen(buff2), 100);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
//"0" ~ "180"
int degree = atoi(buff);
set_servo(degree);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
label2.Text = hScrollBar1.Value.ToString();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(hScrollBar1.Value.ToString());
}
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string data = serialPort1.ReadLine();
textBox2.Text = data;
}
6.이번에는 STM32의 부저를 활용하는데, C#에서 부저에 발생해야하는 주파수값을 직접적으로 전송하고 0을 전송하면 OFF하는걸로 하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
static void Tone(uint32_t Frequency)
{
TIM3->ARR = (1000000UL / Frequency) - 1; //주파수
TIM3->CCR3 = (TIM3->ARR >> 1); //듀티비 50%
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3);
char buff2[100];
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
//0이면 OFF이고 0이 아니면 재생한다
int pitch = atoi(buff);
if(pitch == 0){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, 0);
}else{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, 1);
Tone(pitch);
}
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void button2_MouseDown(object sender, MouseEventArgs e)
{
//도6
if (serialPort1.IsOpen) serialPort1.WriteLine("1047");
}
private void button2_MouseUp(object sender, MouseEventArgs e)
{
//도6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button3_MouseDown(object sender, MouseEventArgs e)
{
//레6
if (serialPort1.IsOpen) serialPort1.WriteLine("1175");
}
private void button3_MouseUp(object sender, MouseEventArgs e)
{
//레6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button4_MouseDown(object sender, MouseEventArgs e)
{
//미6
if (serialPort1.IsOpen) serialPort1.WriteLine("1319");
}
private void button4_MouseUp(object sender, MouseEventArgs e)
{
//미6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button5_MouseDown(object sender, MouseEventArgs e)
{
//파6
if (serialPort1.IsOpen) serialPort1.WriteLine("1397");
}
private void button5_MouseUp(object sender, MouseEventArgs e)
{
//파6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button6_MouseDown(object sender, MouseEventArgs e)
{
//솔6
if (serialPort1.IsOpen) serialPort1.WriteLine("1568");
}
private void button6_MouseUp(object sender, MouseEventArgs e)
{
//솔6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button7_MouseDown(object sender, MouseEventArgs e)
{
//라6
if (serialPort1.IsOpen) serialPort1.WriteLine("1760");
}
private void button7_MouseUp(object sender, MouseEventArgs e)
{
//라6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button8_MouseDown(object sender, MouseEventArgs e)
{
//시6
if (serialPort1.IsOpen) serialPort1.WriteLine("1976");
}
private void button8_MouseUp(object sender, MouseEventArgs e)
{
//시6
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
private void button9_MouseDown(object sender, MouseEventArgs e)
{
//도7
if (serialPort1.IsOpen) serialPort1.WriteLine("2093");
}
private void button9_MouseUp(object sender, MouseEventArgs e)
{
//도7
if (serialPort1.IsOpen) serialPort1.WriteLine("0");
}
7.h스크롤바를 이용해서 0~2500정도의 범위에서 값을 STM32에 전송해서 해당되는 진동의 음을 재생하도록 하시오!
8.스탭모터(28byj-48)의 위치제어값을 C#윈폼에서 전송해서 C#에서 요구한 각도대로 회전하게 하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
uint8_t seq[][4]={
{0,0,0,1}, //1
{0,0,1,1}, //2
{0,0,1,0}, //3
{0,1,1,0}, //4
{0,1,0,0}, //5
{1,1,0,0}, //6
{1,0,0,0}, //7
{1,0,0,1} //8
};
GPIO_TypeDef *mygroup[] = {GPIOC,GPIOC,GPIOC,GPIOD};
uint16_t mypin[] = {GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_2};
int now_pos = 0; //스탭모터가 알고있는 자기의 현재 위치
int target_pos = 0; //녹칸다가 설정한 목표위치
int seq_num = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//타이머 인터럽트 콜백함수
if(now_pos < target_pos){
//시계방향으로 회전해야하는 상황
for(int z = 0; z<4;z++){
HAL_GPIO_WritePin(mygroup[z],mypin[z],seq[seq_num][z]);
}
seq_num++;
if(seq_num >= 8) seq_num = 0;
now_pos++; //시퀀스 1회당 현재위치 1 증가
}else if(now_pos > target_pos){
//반시계방향으로 회전해야 하는 상황
for(int z = 0; z<4;z++){
HAL_GPIO_WritePin(mygroup[z],mypin[z],seq[seq_num][z]);
}
seq_num--;
if(seq_num < 0) seq_num = 7;
now_pos--; //시퀀스 1회당 현재위치 1 감소
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_Base_Start_IT(&htim2); //타이머인터럽트 시작
char buff2[100];
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
//now_pos
sprintf(buff2,"%d\n",now_pos);
HAL_UART_Transmit(&huart2, buff2, strlen(buff2), 100);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
target_pos = atoi(buff);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
/* USER CODE END 4 */
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("0");
}
}
private void button3_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("1024");
}
}
private void button4_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("-1024");
}
}
9.numericupdown을 이용해서 -4096*3 ~ +4096*3의 범위에서 스탭모터의 위치를 결정할 수 있고, 현재 위치값을 STM32가 100밀리초 간격으로 C#에게 전송해서 화면에 나올 수 있도록 하시오!

/* USER CODE BEGIN 0 */
char recv;
char buff[100]; //수신메모리
int pos = 0;
uint8_t seq[][4]={
{0,0,0,1}, //1
{0,0,1,1}, //2
{0,0,1,0}, //3
{0,1,1,0}, //4
{0,1,0,0}, //5
{1,1,0,0}, //6
{1,0,0,0}, //7
{1,0,0,1} //8
};
GPIO_TypeDef *mygroup[] = {GPIOC,GPIOC,GPIOC,GPIOD};
uint16_t mypin[] = {GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_2};
int now_pos = 0; //스탭모터가 알고있는 자기의 현재 위치
int target_pos = 0; //녹칸다가 설정한 목표위치
int seq_num = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//타이머 인터럽트 콜백함수
if(now_pos < target_pos){
//시계방향으로 회전해야하는 상황
for(int z = 0; z<4;z++){
HAL_GPIO_WritePin(mygroup[z],mypin[z],seq[seq_num][z]);
}
seq_num++;
if(seq_num >= 8) seq_num = 0;
now_pos++; //시퀀스 1회당 현재위치 1 증가
}else if(now_pos > target_pos){
//반시계방향으로 회전해야 하는 상황
for(int z = 0; z<4;z++){
HAL_GPIO_WritePin(mygroup[z],mypin[z],seq[seq_num][z]);
}
seq_num--;
if(seq_num < 0) seq_num = 7;
now_pos--; //시퀀스 1회당 현재위치 1 감소
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, &recv, 1); //수신인터럽트 시작
HAL_TIM_Base_Start_IT(&htim2); //타이머인터럽트 시작
char buff2[100];
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
//now_pos
sprintf(buff2,"%d\n",now_pos);
HAL_UART_Transmit(&huart2, buff2, strlen(buff2), 100);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
//usart2 수신인터럽트
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//recv에 1개 읽은 문자가 들어있는 상황이다!
buff[pos] = recv;
pos++;
if(recv == '\n'){
buff[pos] = '\0';
//C#에서 보낸 데이터 한덩어리가 여기서 처리된다!
target_pos = atoi(buff);
pos = 0;
}
//수신인터럽트 다시 시작
HAL_UART_Receive_IT(&huart2, &recv, 1);
}
/* USER CODE END 4 */
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.ReadExisting();
MessageBox.Show("접속완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(numericUpDown1.Value.ToString());
}
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string data = serialPort1.ReadLine();
textBox2.Text = data;
}

