반응형

https://youtube.com/live/5ZRg9t2ga8o

[STM32#27] stm32를 modbus rtu 슬레이브(slave)로 C# winform을 마스터(master)로 설정해서 제어하는 방법(3)!(녹칸다 내맘대로 STM32)

심심한녹칸다의 내맘대로 STM32시리즈이다!

STM32시리즈의 모든 자료는 구글 슬라이드에 작성하고 모두에게 공유되어있음!
https://docs.google.com/presentation/d/1myA5iYbjuKsLWLqtRLKAiRfwUwvqB1d1RGjiMIIgp3I/edit?slide=id.g3b7a28c0ef6_1_0#slide=id.g3b7a28c0ef6_1_0

read holding register부터 이어서 해보기!

 

1.녹칸다의 STM32 26편-6번예제에서 C#으로 만든 modbus rtu마스터가 1개의 word를 read holding register하도록 해놓았는데, 2개이상도 가능하도록 수정하시오!

/* 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[50];
 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);
					  //마스터가 요구한 워드에 갯수만큼 응답한다!
					  //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] = w_reg[start_addr+(i/2)] >> 8; //상위8bit
						  }else{
							  //홀수
							  res[3+i] = w_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);
				  }
			  }
		  }
	  }
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 cnt = ushort.Parse(textBox5.Text);
            ushort[] data = msm.ReadHoldingRegisters(1, addr, cnt);

            richTextBox1.Text += "응답데이터=\n";
            for(int i = 0; i < data.Length; i++)
            {
                richTextBox1.Text += data[i] + "\n";
            }
        }


2.(지금부터 새로운예제로 구현하기) read holding register를 참고해서 read input register를 구현한다음 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 req[8];
 uint8_t res[50];
 //
 uint16_t r_reg[6] = {111,222,333,444,555,666}; //dummy data

 /* 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 == 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 */
 }
using Modbus.Device;

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 addr = ushort.Parse(textBox2.Text);
            ushort cnt = ushort.Parse(textBox3.Text);

            ushort[] data = msm.ReadInputRegisters(1, addr, cnt);

            richTextBox1.Text = "수신데이터=\n";
            for(int i = 0; i < data.Length; i++)
            {
                richTextBox1.Text += data[i] + "\n";
            }
        }


3.예제2에서 만든 수신구문은 마스터가 보낸 request를 8개가 다 올때까지 기다리는 형식이라서 blocking이 너무 심하게 걸린다! RTOS를 적용하고 PB1에 연결된 버튼을 누르면 1씩 다운카운트되고, PA8에 연결된 버튼을 누르면 1씩 업카운트될때, 그 카운터값이 읽기전용 레지스터 0번지에 지정되었다고 할때 마스터에서 그 값을 읽을 수 있도록 하시오!
   -task1 : 마스터에서 modbus rtu명령이 오면 응답한다
   -task2 : 버튼을 누르면 업/다운 카운트한다

/* 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;
}
//task1과 task2가 동시에 참조하는 메모리
 volatile uint16_t r_reg[6] = {0};
/* USER CODE END 0 */

void StartDefaultTask(void *argument)
{
 /* USER CODE BEGIN 5 */
 uint8_t slave_id = 0x01;
 uint8_t tx_buff[100];
 uint8_t req[50];
 uint8_t res[50];
 /* Infinite loop */
 for(;;)
 {
		  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);
				  }
			  }
		  }
	  }
   osDelay(1);
 }
 /* USER CODE END 5 */
}
void StartTask02(void *argument)
{
 /* USER CODE BEGIN StartTask02 */
 /* Infinite loop */
 for(;;)
 {
	  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == 0){
		  r_reg[0]--;
		  osDelay(200);
	  }
	  if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == 0){
		  r_reg[0]++;
		  osDelay(200);
	  }
   osDelay(1); //반드시 있어야함
 }
 /* USER CODE END StartTask02 */
}
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초마다 뭐하면되는냐?
            ushort[] data = msm.ReadInputRegisters(1, 0, 1);
            textBox4.Text = ((short)data[0]).ToString();
        }


4.녹칸다의 STM32쉴드에 붙어있는 가변저항의 값을 읽기전용 레지스터 1번지에 저장을해서 마스터 화면에 출력하시오!
   -task1 : 마스터에서 modbus rtu명령이 오면 응답한다
   -task2 : 버튼을 누르면 업/다운 카운트한다
   -task3 : ADC값 측정하기~

/* 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;
}
//task1과 task2가 동시에 참조하는 메모리
 volatile uint16_t r_reg[6] = {0};
/* USER CODE END 0 */

void StartDefaultTask(void *argument)
{
 /* USER CODE BEGIN 5 */
 uint8_t slave_id = 0x01;
 uint8_t tx_buff[100];
 uint8_t req[50];
 uint8_t res[50];
 /* Infinite loop */
 for(;;)
 {
		  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);
				  }
			  }
		  }
	  }
   osDelay(1);
 }
 /* USER CODE END 5 */
}
void StartTask02(void *argument)
{
 /* USER CODE BEGIN StartTask02 */
 /* Infinite loop */
 for(;;)
 {
	  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == 0){
		  r_reg[0]--;
		  osDelay(200);
	  }
	  if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == 0){
		  r_reg[0]++;
		  osDelay(200);
	  }
   osDelay(1); //반드시 있어야함
 }
 /* USER CODE END StartTask02 */
}
void StartTask03(void *argument)
{
 /* USER CODE BEGIN StartTask03 */
  HAL_ADC_Start(&hadc1); //딱 한번만 호출
 /* Infinite loop */
 for(;;)
 {
	  if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
	  {
		  r_reg[1] = HAL_ADC_GetValue(&hadc1); //0~4095
	  }
	  osDelay(100);
 }
 /* USER CODE END StartTask03 */
}
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, 2);
            textBox4.Text = ((short)data[0]).ToString();
            textBox2.Text = ((short)data[1]).ToString();
        }
반응형
Posted by 덕력킹
,