使用自己绘制的板子通过485与西门子PLC1214C通讯,控制舵机摇摆运动

因为项目需要,需要使用倍福控制器控制舵机,但是舵机是5v,gnd,信号线,所以想到了自己绘制单片机板子,单片机板子在我上一篇博客里面讲过了制作过程,包括,绘制原理图,接线铺铜等等,这一章讲,我是怎么通过plc的485通讯控制舵机摆动的。

一、总体思路

单片机:在这个项目中,单片机作为下位机(动作执行者),将动作写死在状态里。

西门子1214C:1214C作为上位机(选择动作),给单片机执行动作的指令。

通讯:由于485有较强的抗干扰能力,选择使用RS485,作为两者的通讯。

二、单片机代码

由于PLC通过485发送的数据是报文形式的,例如:06 03 00 01 00 01 __ __这种数组,所以单片机是通过485来接收到这些数组,并返回数组,表示PLC与单片机通讯成功。因此,需要串口接收中断,来接收这种数组,并对数组的数据位进行分类,并在主函数中执行对应类别的动作。

cpp 复制代码
#include "rs485.h"
#include "delay.h"
#include "usart.h"
#include "TIM.h"

uint8_t  RS485_RX_BUF[64];
uint8_t  RS485_RX_CNT = 0;
uint16_t Holding_Reg[100];   // 对应40001~40100
extern volatile u8 flag;   // 定义接收完成标志
extern volatile uint8_t state;

#define SLAVE_ADDR 0x06      // 与PLC设定的地址一致

//------------------------------------------------------------------
// 初始化
//------------------------------------------------------------------
void RS485_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    // TX  PA2
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // RX  PA3
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // RS485 DE/RE  PA4
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    RS485_TX_EN = 0;  // 默认接收模式

    // 串口参数
    USART_InitStructure.USART_BaudRate            = 9600;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2, ENABLE);

    // NVIC中断配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 开启接收中断
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
}

//------------------------------------------------------------------
// USART2中断:接收Modbus请求帧
//------------------------------------------------------------------
void USART2_IRQHandler(void)
{
    static uint8_t rx_state = 0;
    static uint8_t rx_len   = 0;

    if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        uint8_t res = USART_ReceiveData(USART2);
				//LCD_ShowNum(100, 200, 1, 2, 24);
        if (rx_state == 0)
        {
            if (res == SLAVE_ADDR)  // 从机地址正确
            {
                RS485_RX_BUF[0] = res;
                rx_len = 1;
                rx_state = 1;
            }
						//LCD_ShowNum(100, 200, RS485_RX_BUF[0], 2, 24);
        }
        else
        {
            RS485_RX_BUF[rx_len++] = res;
            // 典型Modbus主机读命令固定8字节
            if (rx_len >= 8)
            {
							if(RS485_RX_BUF[1] == 0x03)
              {  
								rx_state = 0;
								rx_len = 0;
                Modbus_Process();
								//LCD_ShowNum(100, 200, 9, 2, 24);
                
							}
							if(RS485_RX_BUF[1] == 0x06)
							{
								rx_state = 0;
								rx_len = 0;
                Modbus_Process();
								//LCD_ShowNum(100, 200, 9, 2, 24);
                
							}
            }
        }
        //USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
}

//------------------------------------------------------------------
// RS485发送函数
//------------------------------------------------------------------
void RS485_Send_Data(uint8_t *buf, uint8_t len)
{
    RS485_TX_EN = 1;  // 发送模式
    for (uint8_t i = 0; i < len; i++)
    {
        USART_SendData(USART2, buf[i]);
        while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    }
    while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    //Delay_us(200);     // 延时确保最后字节发出
    RS485_TX_EN = 0;   // 回接收模式
}



uint16_t Modbus_CRC16(uint8_t *buf, uint8_t len)
{
    uint16_t crc = 0xFFFF;
    for (uint8_t i = 0; i < len; i++)
    {
        crc ^= buf[i];
        for (uint8_t j = 0; j < 8; j++)
        {
            if (crc & 0x0001)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc >>= 1;
        }
    }
    return crc;
}

void Modbus_Process(void)
{
	
    uint8_t  slave_id   = RS485_RX_BUF[0];
    uint8_t  func       = RS485_RX_BUF[1];
	
		if(func == 0x03)
		{
			uint16_t start_addr = (RS485_RX_BUF[2] << 8) | RS485_RX_BUF[3];	
			uint16_t reg_count  = (RS485_RX_BUF[4] << 8) | RS485_RX_BUF[5];
			uint16_t crc_recv   = RS485_RX_BUF[6] | (RS485_RX_BUF[7] << 8);
			uint16_t crc_calc   = Modbus_CRC16(RS485_RX_BUF, 6);

			// 校验
			if (slave_id != SLAVE_ADDR) return;
			if (func != 0x03)           return;
		  if (crc_recv != crc_calc)   return;
			if (start_addr + reg_count > 100) return;

			uint8_t txbuf[128];
			txbuf[0] = slave_id;
			txbuf[1] = 0x03;
			txbuf[2] = reg_count * 2;

			for (uint16_t i = 0; i < reg_count; i++)
			{
					uint16_t val = Holding_Reg[start_addr + i];
					txbuf[3 + 2 * i] = val >> 8;
					txbuf[4 + 2 * i] = val & 0xFF;
			}

			uint16_t crc = Modbus_CRC16(txbuf, 3 + reg_count * 2);
			txbuf[3 + reg_count * 2] = crc & 0xFF;        // CRC低字节
			txbuf[4 + reg_count * 2] = crc >> 8;          // CRC高字节

			RS485_Send_Data(txbuf, 5 + reg_count * 2);
		}
		
		if(func == 0x06)
		{
			  uint16_t reg_addr = (RS485_RX_BUF[2] << 8) | RS485_RX_BUF[3];
        uint16_t reg_value = (RS485_RX_BUF[4] << 8) | RS485_RX_BUF[5];
        uint16_t crc_recv  = (RS485_RX_BUF[6] << 8) | RS485_RX_BUF[7];
        uint16_t crc_calc  = Modbus_CRC16(RS485_RX_BUF, 6);

        if (slave_id != SLAVE_ADDR) return;
        if (func != 0x06)           return;
        //if (crc_recv != crc_calc)   return;//过滤杂波
        if (reg_addr >= 100)        return;

        // 写入寄存器
        Holding_Reg[9] = reg_value;
				
				if(Holding_Reg[9] == 0x01)
				{
					flag = 1;
				}
				
				if(Holding_Reg[9] == 0x02)
				{
					flag = 2;
				}
				
				if(Holding_Reg[9] == 0x03)
				{
					flag = 3;
				}
				
        // 回复帧(与请求帧完全相同)
        uint8_t txbuf[8];
        txbuf[0] = slave_id;
        txbuf[1] = 0x06;
        txbuf[2] = reg_addr >> 8;
        txbuf[3] = reg_addr & 0xFF;
        txbuf[4] = reg_value >> 8;
        txbuf[5] = reg_value & 0xFF;

        uint16_t crc = Modbus_CRC16(txbuf, 6);
        txbuf[6] = crc & 0xFF;      // CRC低字节
        txbuf[7] = crc >> 8;        // CRC高字节

        RS485_Send_Data(txbuf, 8);
    }
		}

我在接收到数据后,对发送过来的数据进行判断并赋予状态:

功能码为:03为读取寄存器,06为写入寄存器

数据位为:0x01为停止状态位"flag = 1",0x02为慢速摆动状态位"flag = 2",0x03为快速摆动状态位"flag = 3",在主函数中,写入每一个状态位对应的动作指令即可:

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "PWM.h"
#include "Delay.h"
#include "rs485.h"
#include "TIM.h"
#include "init.h"

volatile u8 flag = 1;
extern uint16_t Holding_Reg[100];
volatile uint8_t state = 0;
#define SLAVE_ADDR 0x06     // 与PLC设定的地址一致


int main()
{
	rcc_init();
//	int state = 0;
	u8 i;
	Encoder_Init();
	RS485_Init();
	PWM_Init();
//flag = 2;
//	state = 5;
//通信调试//
//	while(1)
//	{
//		u8 n[2] = {0x01, 0x02};
//		uint8_t p0[] = {0x00};
//		uint8_t p1[] = {0xFF};
//		uint8_t p2[] = {0x55}; // 01010101
//		uint8_t p3[] = {0xAA}; // 10101010
//		RS485_Send_Data(n, 2);
//		//Modbus_Process();
//	}

		int16_t pos1;
		int16_t pos2;
		int16_t pos3;
    float length1;
    float length2;
		float length3;
    Encoder_Reset1();  // 复位计数器
		Encoder_Reset2();  // 复位计数器
		Encoder_Reset3();  // 复位计数器
	while(1)
	{
    while(flag == 1)//停止旋转,读取霍尔寄存器地址
    {
				pos1 = Encoder_Get1();      // 获取脉冲数(带方向)
				pos2 = Encoder_Get2();      // 获取脉冲数(带方向)
				pos3 = Encoder_Get3();      // 获取脉冲数(带方向)
				length1 = pos1 / 144.0f;     // 假设 20 脉冲 = 1 mm
				length2 = pos2 / 144.0f;
				length3 = pos3 / 144.0f;
				
				Holding_Reg[0] = 0x0051;
				Holding_Reg[1] = 0x0032;
				Holding_Reg[2] = 0x0020;
			
				if(flag == 2)
				{
					break;
				}
			}
			
		while(flag == 2)//慢速旋转
		{
			for(i = 0; i<180; i++)
			{
				Set_Servo_Angle(1, i);
				Set_Servo_Angle(2, i);
				Set_Servo_Angle(3, i);
				Delay_ms(50);
			if(flag != 2)  break;
			}
			if(flag != 2)
			{
				Set_Servo_Angle(1, 90);
				Set_Servo_Angle(2, 90);
				Set_Servo_Angle(3, 90);
				Delay_ms(1000);
				break;
			}
			
			for(i = 180; i>0; i--)
			{
				Set_Servo_Angle(1, i);
				Set_Servo_Angle(2, i);
				Set_Servo_Angle(3, i);
				Delay_ms(50);
				if(flag != 2)  break;
			}
			if(flag != 2)
			{
				Set_Servo_Angle(1, 90);
				Set_Servo_Angle(2, 90);
				Set_Servo_Angle(3, 90);
				Delay_ms(1000);
				break;
			}
		}
			
			while(flag == 3)//快速旋转
			{
				Set_Servo_Angle(1, 0);
				Set_Servo_Angle(2, 0);
				Set_Servo_Angle(3, 0);
				Delay_ms(2000);
				if(flag != 3)
				{
					Set_Servo_Angle(1, 90);
					Set_Servo_Angle(2, 90);
					Set_Servo_Angle(3, 90);
					Delay_ms(1000);
					break;
				}
				
				Set_Servo_Angle(1, 180);
				Set_Servo_Angle(2, 180);
				Set_Servo_Angle(3, 180);
				Delay_ms(2000);
				if(flag != 3)
				{
					Set_Servo_Angle(1, 90);
					Set_Servo_Angle(2, 90);
					Set_Servo_Angle(3, 90);
					Delay_ms(1000);
					break;
				}
			}
	}
}

我在写第一个版本的时候遇到了两个问题,一个是不能返回数据,另一个是接受数据后卡死;

第一个是因为USART_ClearITPendingBit(USART2, USART_IT_RXNE);这个删除标志位的代码,上面接收完数据后会自动删除串口中断标志位,所以我加入这段代码意味着我将下一次中断删除了,会加入0xff的干扰,导致不能返回数据

第二个接收数据后卡死是因为在处理功能码为06写入时,校验位赋值错误,导致终端里面计算错误失效,导致终端卡死。

三、PLC发送代码

首先,进行配置通讯

其次,配置主站

最后,就可以运行啦,运行的时候你会发现,非常的流畅,不会中断,好用的很------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------(●ˇ∀ˇ●)------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------O(∩_∩)O------------------------------------------------------------------------------

相关推荐
民乐团扒谱机7 小时前
十字路口交通信号灯控制器设计(Multisim 电路 + Vivado 仿真)
单片机·fpga开发·verilog·状态机·仿真·时序逻辑·multism
bai5459367 小时前
STM32 CubeIDE 按键控制LED
stm32·单片机·嵌入式硬件
小π军7 小时前
51单片机第2讲:数码管
单片机·嵌入式硬件·51单片机
光子物联单片机7 小时前
STM32单片机开发入门(十一)STM32CubeIDE下载安装及开发调试说明
c语言·stm32·单片机·嵌入式硬件·mcu
cat_milk7 小时前
【潘多拉 STM32L475 IOT开发指南】【基础】1.LED
stm32·单片机·物联网
TEC_INO7 小时前
Stm32_2:蜂鸣器、按键、继电器
stm32·单片机·嵌入式硬件
yugi9878387 小时前
实现STM32读取INA226电流值并通过串口发送给HMI串口屏显示
stm32·单片机·嵌入式硬件
云数据构建师8 小时前
TB67S579FTG(O,EL)这是一款非常经典和流行的 “双极步进电机驱动器IC”,主要用于控制步进电机。
单片机·嵌入式硬件
星期天28 小时前
1.6中断系统原理和硬件连接
stm32·单片机·嵌入式硬件·stm32江科大
兆龙电子单片机设计8 小时前
【STM32项目开源】STM32单片机智能心率手环系统
stm32·单片机·物联网·开源·毕业设计