반응형

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
            {

            }
            
        }
반응형
Posted by 덕력킹
,