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

https://youtube.com/live/YIJnO-q8EEM
[STM32#28] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(4)!(녹칸다 내맘대로 STM32)
심심한녹칸다의 내맘대로 STM32시리즈이다!
STM32시리즈의 모든 자료는 구글 슬라이드에 작성하고 모두에게 공유되어있음!
https://docs.google.com/presentation/d/1myA5iYbjuKsLWLqtRLKAiRfwUwvqB1d1RGjiMIIgp3I/edit?slide=id.g3bace9f0d28_4_189#slide=id.g3bace9f0d28_4_189
read input register부터 이어서 해보기!
1.녹칸다의 STM32시리즈 27-4번예제에서 가변저항, CDS, NTC서미스터의 값을 레지스터에 기록하고 C#윈폼 화면에 modbus rtu로 읽어와서 출력하시오! [참고]
-가변저항(12bit), CDS(12bit), ntc서미스터(온도/소수점이있겠지?)

/* 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;
}
#define R1 10000
#define c1 1.009249522e-03
#define c2 2.378405444e-04
#define c3 2.019202697e-07
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t req[50];
uint8_t res[50];
//modbus rtu에서 읽기전용 레지스터
uint16_t r_reg[6] = {0};
uint32_t t = 0;
float logR2, R2, T, Tc, Tf;
uint16_t adc_value0 = 0;
uint16_t adc_value1 = 0;
uint16_t adc_value2 = 0;
/* USER CODE END 2 */
while (1)
{
//100밀리초 간격으로 adc값을 읽어서 레지스터(uint16_t로 된 배열)에 대입한다
if(HAL_GetTick() - t >= 100){
t = HAL_GetTick();
//PA0 ADC1 RANK1
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_value0 = HAL_ADC_GetValue(&hadc1); //가변저항
//PA1 ADC1 RANK2
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_value1 = HAL_ADC_GetValue(&hadc1); //CDS
//PA4 ADC1 RANK3
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_value2 = HAL_ADC_GetValue(&hadc1); //NTC서미스터
//온도계산
R2 = R1 * (4095.0 / adc_value2 - 1.0);
logR2 = log(R2);
T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2));
Tc = T - 273.15; //온도값
//modbus레지스터에 대입
r_reg[0] = adc_value0;
r_reg[1] = adc_value1;
r_reg[2] = (uint16_t)(Tc*100);
}
//read input register에 대응하는 부분
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 == 0x04){
//read input 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);
//마스터가 요구한 워드에 갯수만큼 응답한다!
//0~2번까지는 자리가 고정이다!
res[0] = id;
res[1] = fc;
res[2] = count * 2; //count는 무조건 1인상태!
//count == 2
//반복문
for(int i = 0;i < count * 2;i++){
//i의 값을 2로 나눠서 나머지가 없으면 짝수다!
if(i%2 == 0){
//짝수
res[3+i] = r_reg[start_addr+(i/2)] >> 8; //상위8bit
}else{
//홀수
res[3+i] = r_reg[start_addr+(i/2)] & 0xFF; //하위8bit
}
}
//count가 2이라면~ [0,1,2,3,4,5,6] [7,8]
//CRC16의 위치는 3+count*2, 4+count*2
//CRC16이전의 데이터로 체크섬을 계산한다!
uint16_t res_crc16 = cal_crc(res,3+count*2);
res[3+count*2] = (res_crc16 & 0xFF);
res[4+count*2] = (res_crc16 >> 8);
//3+count*2에다가 체크섬 갯수 2개를 더한개 최종보낼 데이터의 길이
HAL_UART_Transmit(&huart1, res, 5+count*2, 100);
HAL_UART_Transmit(&huart2, "RES DATA = ", 11, 100);
for(int i = 0;i<5+count*2;i++){
sprintf(tx_buff,"%02X, ",res[i]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
}
HAL_UART_Transmit(&huart2, "\n", 1, 100);
}else{
HAL_UART_Transmit(&huart2, "CRC16 ERROR!\n", 13, 100);
}
}
}
}
/*
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
ModbusSerialMaster msm;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 9600;
serialPort1.Open();
if (serialPort1.IsOpen)
{
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("접속완료");
}
else
{
MessageBox.Show("접속실패");
}
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
//타이머인터럽트가 0.1초마다 뭐하면되는냐?
//0번지부터 2개를 읽는다!
ushort[] data = msm.ReadInputRegisters(1, 0, 3);
textBox4.Text = data[0].ToString(); //가변저항
textBox2.Text = data[1].ToString(); //CDS
textBox3.Text = (data[2]/100.0f).ToString(); //온도
}
2.이번에는 프로토콜 자료를 참고해서 write multiple coil명령으로 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 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t recv; //1개씩 읽는 변수
uint8_t pos = 0;
uint8_t req[50];
uint8_t res[50];
//modbus rtu의 write coil
uint8_t mycoil[8] = {0};
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};
/* USER CODE END 2 */
while (1)
{
for(int i = 0;i<8;i++){
HAL_GPIO_WritePin(mygroup[i], mypin[i], mycoil[i]);
}
//write multiple coil은 길이가 확정되어있지 않다!
if(HAL_UART_Receive(&huart1, &recv, 1, 500) == HAL_OK){
pos = 0;
req[pos++] = recv;
while(HAL_UART_Receive(&huart1, &recv, 1, 100) == HAL_OK){
req[pos++] = recv;
}
uint8_t id = req[0];
uint8_t fc = req[1];
uint16_t addr = (req[2] << 8) | req[3];
uint16_t qty = (req[4] << 8) | req[5];
uint8_t cnt = req[6];
//CRC16의 위치
//req 7+cnt개
uint16_t crc1 = cal_crc(req,7+cnt);
uint16_t crc2 = (req[7+cnt+1] << 8) | req[7+cnt];
if(crc1 == crc2){
//데이터 해석
for(int i = 0;i < qty;i++){
uint8_t state = (req[7+(i/8)] >> (i%8)) & 0x01;
mycoil[i] = state;
sprintf(tx_buff,"%d = %d\n",i,state);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
}
//응답
res[0] = req[0];
res[1] = req[1];
res[2] = req[2];
res[3] = req[3];
res[4] = req[4];
res[5] = req[5];
uint16_t crc3 = cal_crc(res,6);
res[6] = crc3 & 0x00FF; //crc3의 하위 8bit
res[7] = crc3 >> 8; //crc3의 상위 8bit
HAL_UART_Transmit(&huart1, res, 8, 100);
}
/*
//흐름이 끊어짐(지금 STM32가 읽은 데이터 갯수는 pos byte)
for(int i = 0;i<pos;i++){
sprintf(tx_buff,"%02X, ",req[i]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
}
*/
}
/*
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
ModbusSerialMaster msm;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 9600;
serialPort1.Open();
if (serialPort1.IsOpen)
{
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("접속완료");
}
else
{
MessageBox.Show("접속실패");
}
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
//타이머인터럽트가 0.1초마다 뭐하면되는냐?
//0번지부터 2개를 읽는다!
ushort[] data = msm.ReadInputRegisters(1, 0, 3);
textBox4.Text = data[0].ToString(); //가변저항
textBox2.Text = data[1].ToString(); //CDS
textBox3.Text = (data[2]/100.0f).ToString(); //온도
}
3.이번에는 write multiple register명령을 stm32에 전송해서 putty에 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 */
uint8_t slave_id = 0x01;
uint8_t tx_buff[100];
uint8_t recv; //1개씩 읽는 변수
uint8_t pos = 0;
uint8_t req[50];
uint8_t res[50];
//modbus rtu의 write register
uint16_t w_reg[10];
/* USER CODE END 2 */
while (1)
{
//write multiple coil은 길이가 확정되어있지 않다!
if(HAL_UART_Receive(&huart1, &recv, 1, 500) == HAL_OK){
pos = 0;
req[pos++] = recv;
while(HAL_UART_Receive(&huart1, &recv, 1, 100) == HAL_OK){
req[pos++] = recv;
}
uint8_t id = req[0];
uint8_t fc = req[1];
uint16_t addr = (req[2] << 8) | req[3];
uint16_t qty = (req[4] << 8) | req[5];
uint8_t cnt = req[6];
for(int i =0;i<cnt;i++){
if(i%2 == 0){
w_reg[i/2] = (req[7+i]<<8);
}else{
w_reg[i/2] = w_reg[i/2] | req[7+i];
}
}
//출력테스트
for(int i = 0;i<qty;i++){
sprintf(tx_buff,"%d = %d\n",i,w_reg[i]);
HAL_UART_Transmit(&huart2, tx_buff, strlen(tx_buff), 100);
}
//응답
res[0] = req[0];
res[1] = req[1];
res[2] = req[2];
res[3] = req[3];
res[4] = req[4];
res[5] = req[5];
uint16_t crc3 = cal_crc(res,6);
res[6] = crc3 & 0x00FF; //crc3의 하위 8bit
res[7] = crc3 >> 8; //crc3의 상위 8bit
HAL_UART_Transmit(&huart1, res, 8, 100);
}
/*
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
ModbusSerialMaster msm;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//접속버튼 클릭
serialPort1.PortName = textBox1.Text;
serialPort1.BaudRate = 9600;
serialPort1.Open();
if (serialPort1.IsOpen)
{
msm = ModbusSerialMaster.CreateRtu(serialPort1);
MessageBox.Show("접속완료");
}
else
{
MessageBox.Show("접속실패");
}
}
private void button2_Click(object sender, EventArgs e)
{
ushort[] data = new ushort[6];
data[0] = (ushort)numericUpDown1.Value;
data[1] = (ushort)numericUpDown2.Value;
data[2] = (ushort)numericUpDown3.Value;
data[3] = (ushort)numericUpDown4.Value;
data[4] = (ushort)numericUpDown5.Value;
data[5] = (ushort)numericUpDown6.Value;
try
{
msm.WriteMultipleRegisters(1, 0, data);
}
catch
{
}
}반응형


