[STM32#26] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(2)!(녹칸다 내맘대로 STM32)
프로그래밍/STM32 2026. 1. 7. 16:26
https://youtube.com/live/0GP7utrIFTw
[STM32#26] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(2)!(녹칸다 내맘대로 STM32)
심심한녹칸다의 내맘대로 STM32시리즈이다!
STM32시리즈의 모든 자료는 구글 슬라이드에 작성하고 모두에게 공유되어있음!
https://docs.google.com/presentation/d/1myA5iYbjuKsLWLqtRLKAiRfwUwvqB1d1RGjiMIIgp3I/edit?slide=id.g3b2751bc412_11_154#slide=id.g3b2751bc412_11_154
마스터인 C#윈폼에서는 nmodbus4 라이브러리를 적용해보도록 하자!
1.예제25편-2에서 만든 예제를 마스터쪽에 nmodbus4라이브러리를 적용해서 stm32가 정상적으로 작동되는지 보이시오!

/* 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, 500) == 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);
}
}
}
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
//coil 0번을 ON한다!
msm.WriteSingleCoil(1, 0, true);
}
private void button3_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 0, false);
}
private void button4_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, true);
}
private void button5_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, false);
}
private void button6_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, true);
}
private void button7_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, false);
}
private void button8_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, true);
}
private void button9_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, false);
}
private void button10_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, true);
}
private void button11_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, false);
}
private void button12_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, true);
}
private void button13_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, false);
}
private void button14_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, true);
}
private void button15_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, false);
}
private void button16_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, true);
}
private void button17_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, false);
}
2.예제1에 더해서 마스터쪽에서 readcoil을 실행했을때 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];
uint8_t res[6]; //read coils 전용
uint8_t write_coil[1];
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 500) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
if(fc == 0x01){
//read coils
uint16_t start_addr = (req[2] << 8) | req[3];
uint16_t count = (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){
//read coil 응답데이터 만들기
//write_coil[0]여기에 순서에 맞게 bit 상태를 넣으면 됨!
write_coil[0] = 0;
for(int i = 0; i < count; i++)
{
if(mygroup[i]->ODR & mypin[i])
write_coil[0] |= (1 << i);
}
sprintf(tx_buff,"id=%02X, fc=%02X, start_addr=%04X, cnt=%04X, crc=%04X, data=%d\n",id,fc,start_addr,count,crc16,write_coil[0]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
res[0] = id;
res[1] = fc;
res[2] = 1; //응답데이터는 1byte이다!
res[3] = write_coil[0];
uint16_t res_crc = cal_crc(res,4);
res[4] = res_crc & 0xFF; //res_crc의 하위8bit
res[5] = res_crc >> 8; //res_crc의 상위8bit
HAL_UART_Transmit(&huart1, res, 6, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}else if(fc == 0x05){
//write single coil
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);
}
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
//coil 0번을 ON한다!
msm.WriteSingleCoil(1, 0, true);
}
private void button3_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 0, false);
}
private void button4_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, true);
}
private void button5_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, false);
}
private void button6_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, true);
}
private void button7_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, false);
}
private void button8_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, true);
}
private void button9_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, false);
}
private void button10_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, true);
}
private void button11_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, false);
}
private void button12_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, true);
}
private void button13_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, false);
}
private void button14_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, true);
}
private void button15_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, false);
}
private void button16_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, true);
}
private void button17_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, false);
}
private void button18_Click(object sender, EventArgs e)
{
//라이브러리가 stm32에게 0번지부터 8개의 쓰기코일값을 요구한다!
bool[] data = msm.ReadCoils(1, 0, 8);
string text = "";
for(int i = 0; i < 8; i++)
{
text += data[i].ToString() + ", ";
}
richTextBox1.Text += text + "\n";
}
3.예제2번에서 마스터쪽에 STM32쪽 LED가 켜져있으면 ON버튼의 배경색을 녹색으로 하고, OFF상태면 OFF버튼의 배경색을 빨간색으로 하시오! 여기서 C#의 timer를 하나 추가해서 100밀리초 간격으로 read coils 명령을 수행하도록하시오!

/* 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];
uint8_t res[6]; //read coils 전용
uint8_t write_coil[1];
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 500) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
if(fc == 0x01){
//read coils
uint16_t start_addr = (req[2] << 8) | req[3];
uint16_t count = (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){
//read coil 응답데이터 만들기
//write_coil[0]여기에 순서에 맞게 bit 상태를 넣으면 됨!
write_coil[0] = 0;
for(int i = 0; i < count; i++)
{
if(mygroup[i]->ODR & mypin[i])
write_coil[0] |= (1 << i);
}
sprintf(tx_buff,"id=%02X, fc=%02X, start_addr=%04X, cnt=%04X, crc=%04X, data=%d\n",id,fc,start_addr,count,crc16,write_coil[0]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
res[0] = id;
res[1] = fc;
res[2] = 1; //응답데이터는 1byte이다!
res[3] = write_coil[0];
uint16_t res_crc = cal_crc(res,4);
res[4] = res_crc & 0xFF; //res_crc의 하위8bit
res[5] = res_crc >> 8; //res_crc의 상위8bit
HAL_UART_Transmit(&huart1, res, 6, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}else if(fc == 0x05){
//write single coil
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);
}
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
//coil 0번을 ON한다!
msm.WriteSingleCoil(1, 0, true);
}
private void button3_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 0, false);
}
private void button4_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, true);
}
private void button5_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 1, false);
}
private void button6_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, true);
}
private void button7_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 2, false);
}
private void button8_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, true);
}
private void button9_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 3, false);
}
private void button10_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, true);
}
private void button11_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 4, false);
}
private void button12_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, true);
}
private void button13_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 5, false);
}
private void button14_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, true);
}
private void button15_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 6, false);
}
private void button16_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, true);
}
private void button17_Click(object sender, EventArgs e)
{
msm.WriteSingleCoil(1, 7, false);
}
private void button18_Click(object sender, EventArgs e)
{
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
//0.1초마다 이부분이 실행되는데 뭐할래?
//라이브러리가 stm32에게 0번지부터 8개의 쓰기코일값을 요구한다!
bool[] data = msm.ReadCoils(1, 0, 8);
if (data[0])
{
button2.BackColor = Color.Green;
button3.BackColor = SystemColors.Control;
}
else
{
button2.BackColor = SystemColors.Control;
button3.BackColor = Color.Red;
}
if (data[1])
{
button4.BackColor = Color.Green;
button5.BackColor = SystemColors.Control;
}
else
{
button4.BackColor = SystemColors.Control;
button5.BackColor = Color.Red;
}
if (data[2])
{
button6.BackColor = Color.Green;
button7.BackColor = SystemColors.Control;
}
else
{
button6.BackColor = SystemColors.Control;
button7.BackColor = Color.Red;
}
if (data[3])
{
button8.BackColor = Color.Green;
button9.BackColor = SystemColors.Control;
}
else
{
button8.BackColor = SystemColors.Control;
button9.BackColor = Color.Red;
}
if (data[4])
{
button10.BackColor = Color.Green;
button11.BackColor = SystemColors.Control;
}
else
{
button10.BackColor = SystemColors.Control;
button11.BackColor = Color.Red;
}
if (data[5])
{
button12.BackColor = Color.Green;
button13.BackColor = SystemColors.Control;
}
else
{
button12.BackColor = SystemColors.Control;
button13.BackColor = Color.Red;
}
if (data[6])
{
button14.BackColor = Color.Green;
button15.BackColor = SystemColors.Control;
}
else
{
button14.BackColor = SystemColors.Control;
button15.BackColor = Color.Red;
}
if (data[7])
{
button16.BackColor = Color.Green;
button17.BackColor = SystemColors.Control;
}
else
{
button16.BackColor = SystemColors.Control;
button17.BackColor = Color.Red;
}
}
4.이번예제는 지금까지 했던 coil내용을 다 제거하고 write single register에 대해서 예제를 만들어보도록 한다! 마스터쪽에서 설정한 값을 stm32가 받아서 putty에 단순 출력하는 예시를 보이시오!

/* 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 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[8];
uint16_t w_reg[6] = {0}; //6word 정도의 레지스터가 있다고 가정함
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 500) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
//fc가 0x06이 올 예정!
uint16_t reg_addr = (req[2] << 8) | req[3];
uint16_t reg_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,reg_addr,reg_data,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
w_reg[reg_addr] = reg_data;
//레지스터가 6개 있으니까 6개의 값을 출력한다!
sprintf(tx_buff,"w_reg=%d, %d, %d, %d, %d, %d\n",w_reg[0],w_reg[1],w_reg[2],w_reg[3],w_reg[4],w_reg[5]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//응답은 req를 그대로 응답하면 된다!
HAL_UART_Transmit(&huart1, req, 8, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
ushort addr = ushort.Parse(textBox2.Text);
ushort value = ushort.Parse(textBox3.Text);
msm.WriteSingleRegister(1, addr, value);
}
5.마스터쪽에서 STM32의 쓰기레지스터 0번지에 값을 쓰면 녹칸다의 STM32쉴드에 붙어있는 FND모듈에 그 값이 출력되도록 하시오!

/* 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;
}
//여기 추가해야하는 코드는 FND모듈에추가해야하는 코드 그대로 복붙하기!
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[8];
uint16_t w_reg[6] = {0}; //6word 정도의 레지스터가 있다고 가정함
tm1637Init();
tm1637SetBrightness(4);
tm1637DisplayDecimalTrim(0, 0);
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 500) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
//fc가 0x06이 올 예정!
uint16_t reg_addr = (req[2] << 8) | req[3];
uint16_t reg_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,reg_addr,reg_data,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
w_reg[reg_addr] = reg_data;
if(reg_addr == 0)
tm1637DisplayDecimalTrim(w_reg[0], 0);
//레지스터가 6개 있으니까 6개의 값을 출력한다!
sprintf(tx_buff,"w_reg=%d, %d, %d, %d, %d, %d\n",w_reg[0],w_reg[1],w_reg[2],w_reg[3],w_reg[4],w_reg[5]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//응답은 req를 그대로 응답하면 된다!
HAL_UART_Transmit(&huart1, req, 8, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
ushort addr = ushort.Parse(textBox2.Text);
ushort value = ushort.Parse(textBox3.Text);
msm.WriteSingleRegister(1, addr, value);
}
6.이번에는 예제4에 더해서 쓰기 레지스터에서 값을 읽는 명령인 read holding register를 구현해서 C#화면에 출력하도록 한다! (마스터는 1워드의 데이터만 요구할수있는 제약조건을 주고 다음주 예제부터 다시 이어서 구현하도록 함 시간관계상)

/* 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 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[8];
uint8_t res[17];
uint16_t w_reg[6] = {0}; //6word 정도의 레지스터가 있다고 가정함
/* USER CODE END 2 */
while (1)
{
//usart1은 RS485통신, usart2는 PC와 통신
//usart1으로 수신해서 usart2로 결과를 송신한다!
if(HAL_UART_Receive(&huart1, req, 8, 500) == HAL_OK){
//응답예시데이터
uint8_t id = req[0];
//내 id에게 req가 왔을때만 res를 전송한다!
if(id == slave_id){
uint8_t fc = req[1];
if(fc == 0x06){
uint16_t reg_addr = (req[2] << 8) | req[3];
uint16_t reg_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,reg_addr,reg_data,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
w_reg[reg_addr] = reg_data;
//레지스터가 6개 있으니까 6개의 값을 출력한다!
sprintf(tx_buff,"w_reg=%d, %d, %d, %d, %d, %d\n",w_reg[0],w_reg[1],w_reg[2],w_reg[3],w_reg[4],w_reg[5]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//응답은 req를 그대로 응답하면 된다!
HAL_UART_Transmit(&huart1, req, 8, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}else if(fc == 0x03){
//read holding register
uint16_t start_addr = (req[2] << 8) | req[3];
uint16_t count = (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,start_addr,count,crc16);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
//마스터가 요구한 워드에 갯수만큼 응답한다!
res[0] = id;
res[1] = fc;
res[2] = count * 2;
res[3] = (w_reg[start_addr] >> 8);
res[4] = (w_reg[start_addr] & 0xFF);
uint16_t res_crc16 = cal_crc(res,5);
res[5] = (res_crc16 & 0xFF);
res[6] = (res_crc16 >> 8);
HAL_UART_Transmit(&huart1, res, 7, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
using Modbus.Device; //네임스페이스를 추가!
//nmodbus4 객체선언(modbus rtu전용)
ModbusSerialMaster msm;
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.BaudRate = 9600;
serialPort1.PortName = textBox1.Text;
serialPort1.Open();
if (serialPort1.IsOpen)
{
//접속이 완료된 serialport 객체를 라이브러리에게 넘긴다!
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("연결완료!");
}
}
private void button2_Click(object sender, EventArgs e)
{
ushort addr = ushort.Parse(textBox2.Text);
ushort value = ushort.Parse(textBox3.Text);
msm.WriteSingleRegister(1, addr, value);
}
private void button3_Click(object sender, EventArgs e)
{
ushort addr = ushort.Parse(textBox4.Text);
ushort[] data = msm.ReadHoldingRegisters(1, addr, 1);
richTextBox1.Text += "응답데이터=" + data[0].ToString() + "\n";
}

