使用自己绘制的板子通过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------------------------------------------------------------------------------

相关推荐
kaikaile19951 小时前
基于DSP28335与AD7606的采样程序实现
单片机·嵌入式硬件
Joshua-a1 小时前
STM32嵌入式开发核心:volatile与寄存器操作详解
单片机·嵌入式硬件
国科安芯7 小时前
MCU芯片AS32A601与INA226芯片精确测量实现与应用
网络·单片机·嵌入式硬件·架构·安全性测试
一支闲人8 小时前
STM32 CAN外设1
stm32·单片机·嵌入式硬件·基础知识·cna协议
HanLop8 小时前
51单片机入门
单片机·嵌入式硬件·51单片机
Bona Sun16 小时前
单片机手搓掌上游戏机(十一)—esp8266运行gameboy模拟器之硬件连接
c语言·c++·单片机·游戏机
云山工作室16 小时前
基于物联网的智能楼宇门禁系统
单片机·物联网·毕业设计·课程设计·毕设
d111111111d18 小时前
SPI通信协议--在STM32中介绍(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
up向上up20 小时前
基于STM32的电子钟万年历Proteus仿真设计_LCD1602显示
stm32·单片机·proteus