[STM32#25] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(1)!(녹칸다 내맘대로 STM32)
프로그래밍/STM32 2026. 1. 5. 23:16반응형

https://youtube.com/live/xWxbcANp3ac
[STM32#25] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(1)!(녹칸다 내맘대로 STM32)
심심한녹칸다의 내맘대로 STM32시리즈이다!
STM32시리즈의 모든 자료는 구글 슬라이드에 작성하고 모두에게 공유되어있음!
https://docs.google.com/presentation/d/1myA5iYbjuKsLWLqtRLKAiRfwUwvqB1d1RGjiMIIgp3I/edit?slide=id.g3b25d818391_8_0#slide=id.g3b25d818391_8_0
이제 stm32가 마스터가 아니라 슬레이브가 되는 것입니다~~!
1.C#윈폼에서 버튼을 2개 생성하고 ON버튼을 누르면 STM32의 PB3의 LED가 켜지고, OFF버튼을 누르면 꺼지도록 하시오!

/* USER CODE BEGIN 0 */
uint16_t cal_crc(uint8_t *data, uint8_t len)
{
uint16_t crc = 0xFFFF;
for(uint8_t i = 0; i < len; i++)
{
crc ^= data[i]; // LSB부터 XOR
for(uint8_t j = 0; j < 8; j++)
{
if(crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001; // Modbus polynomial
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
//녹칸다의 STM32쉴드의 8개의 LED를 배열로 뭉쳐놓은것!
GPIO_TypeDef *mygroup[] = {GPIOB,GPIOB,GPIOB,GPIOB,GPIOB,GPIOB,GPIOC,GPIOB};
uint16_t mypin[] = {GPIO_PIN_3,GPIO_PIN_13,GPIO_PIN_5,GPIO_PIN_14,GPIO_PIN_4,GPIO_PIN_15,GPIO_PIN_4,GPIO_PIN_7};
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[8];
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 100) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
uint16_t coil_addr = (req[2] << 8) | req[3];
uint16_t coil_data = (req[4] << 8) | req[5];
uint16_t crc16 = (req[7] << 8) | req[6];
//STM32가 CRC16을 계산함!
uint16_t my_crc16 = cal_crc(req,6);
if(crc16 == my_crc16){
sprintf(tx_buff,"id=%02X, fc=%02X, addr=%04X, data=%04X, crc=%04X\n",id,fc,coil_addr,coil_data,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//write single coil명령어 수행
if(coil_data == 0xFF00){
//ON
HAL_GPIO_WritePin(mygroup[coil_addr], mypin[coil_addr], 1);
}else{
//OFF
HAL_GPIO_WritePin(mygroup[coil_addr], mypin[coil_addr], 0);
}
//응답전송! 그런데 FC=0x05는 req와 res가 같다!
HAL_UART_Transmit(&huart1, req, 8, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
byte[] req = { 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x8C, 0x3A };
serialPort1.Write(req, 0, 8);
}
}
private void button3_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
byte[] req = { 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xCA };
serialPort1.Write(req, 0, 8);
}
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
//슬레이브로부터 8개의 데이터가 응답으로 올 예정이다!
byte[] res = new byte[8];
int pos = 0;
for (int i = 0; i < 8; i++)
{
res[pos++] = (byte)serialPort1.ReadByte();
}
richTextBox1.Text += "[응답] ";
for(int i = 0; i < 8; i++)
{
richTextBox1.Text += res[i].ToString("X") + ", ";
}
richTextBox1.Text += "\n";
}
2.예제1번에서 PB3번 LED만 제어했던것을 녹칸다의 STM32쉴드에 붙어있는 8개의 LED를 제어할 수 있는 기능으로 확장하시오!

/* USER CODE BEGIN 0 */
uint16_t cal_crc(uint8_t *data, uint8_t len)
{
uint16_t crc = 0xFFFF;
for(uint8_t i = 0; i < len; i++)
{
crc ^= data[i]; // LSB부터 XOR
for(uint8_t j = 0; j < 8; j++)
{
if(crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001; // Modbus polynomial
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
//녹칸다의 STM32쉴드의 8개의 LED를 배열로 뭉쳐놓은것!
GPIO_TypeDef *mygroup[] = {GPIOB,GPIOB,GPIOB,GPIOB,GPIOB,GPIOB,GPIOC,GPIOB};
uint16_t mypin[] = {GPIO_PIN_3,GPIO_PIN_13,GPIO_PIN_5,GPIO_PIN_14,GPIO_PIN_4,GPIO_PIN_15,GPIO_PIN_4,GPIO_PIN_7};
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[8];
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 100) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
uint16_t coil_addr = (req[2] << 8) | req[3];
uint16_t coil_data = (req[4] << 8) | req[5];
uint16_t crc16 = (req[7] << 8) | req[6];
//STM32가 CRC16을 계산함!
uint16_t my_crc16 = cal_crc(req,6);
if(crc16 == my_crc16){
sprintf(tx_buff,"id=%02X, fc=%02X, addr=%04X, data=%04X, crc=%04X\n",id,fc,coil_addr,coil_data,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//write single coil명령어 수행
if(coil_data == 0xFF00){
//ON
HAL_GPIO_WritePin(mygroup[coil_addr], mypin[coil_addr], 1);
}else{
//OFF
HAL_GPIO_WritePin(mygroup[coil_addr], mypin[coil_addr], 0);
}
//응답전송! 그런데 FC=0x05는 req와 res가 같다!
HAL_UART_Transmit(&huart1, req, 8, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(0, true);
}
private void button3_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(0, false);
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
//슬레이브로부터 8개의 데이터가 응답으로 올 예정이다!
byte[] res = new byte[8];
int pos = 0;
for (int i = 0; i < 8; i++)
{
res[pos++] = (byte)serialPort1.ReadByte();
}
richTextBox1.Text += "[응답] ";
for(int i = 0; i < 8; i++)
{
richTextBox1.Text += res[i].ToString("X") + ", ";
}
richTextBox1.Text += "\n";
}
UInt16 cal_crc(byte[] data, int len)
{
UInt16 crc = 0xFFFF;
for (int pos = 0; pos < len; pos++)
{
crc ^= data[pos];
for (int i = 0; i < 8; i++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
void write_single_coil(UInt16 coil_num, bool state)
{
byte[] req = { 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 };
if (state == false)
{
req[4] = 0x00;
}
req[2] = (byte)(coil_num >> 8);
req[3] = (byte)(coil_num & 0xFF);
UInt16 crc = cal_crc(req, 6);
req[6] = (byte)(crc & 0xFF);
req[7] = (byte)(crc >> 8);
serialPort1.Write(req, 0, 8);
}
private void button4_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(1, true);
}
private void button5_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(1, false);
}
private void button6_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(2, true);
}
private void button7_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(2, false);
}
private void button8_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(3, true);
}
private void button9_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(3, false);
}
private void button10_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(4, true);
}
private void button11_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(4, false);
}
private void button12_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(5, true);
}
private void button13_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(5, false);
}
private void button14_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(6, true);
}
private void button15_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(6, false);
}
private void button16_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(7, true);
}
private void button17_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) write_single_coil(7, false);
}반응형


