当前位置: 首页 > news >正文

网站模板源码免费下载/外贸推广有哪些好的方式

网站模板源码免费下载,外贸推广有哪些好的方式,爱做网站网址,中央人民政府网举报STM32HAL库RS485-ModBus协议控制伺服电机 一个月前,接手了一个学长的毕设小车,小车采用rs485通信的modbus协议驱动轮毂电机,与往常我学习的pwm控制电机方法大相径庭,在这里以这篇博客记录下该学习过程。 小车主要架构 电机型号 …

STM32HAL库RS485-ModBus协议控制伺服电机

一个月前,接手了一个学长的毕设小车,小车采用rs485通信的modbus协议驱动轮毂电机,与往常我学习的pwm控制电机方法大相径庭,在这里以这篇博客记录下该学习过程。

小车主要架构

image-20230422152803354

电机型号

中菱轮毂电机

轮毂驱动器ZLAC8015D

  • ZLAC8015D 的 RS485 支持 Modbus RTU 协议。
  • 驱动器地址为 0-127 可设,默认为 1;
  • 波特率 9600、19200、38400、57600、115200、128000、256000 等 7 种, 可通过软件设置。

默认 115200; 数据位 8,无奇偶校验,停止位 1

RS485通信

RS485接口组成的半双工网络,一般是两线制,多采用屏蔽双绞线传输,这种接线方式为总线式拓扑结构在同一总线上最多可以挂接32个结点。我们知道,最初数据是模拟信号输出简单过程量,后来仪表接口是RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,随后出现的RS485解决了这个问题。为此本文通过问答的形式详细介绍RS485接口。

RS485_RE为高电平的时候,DE为高电平有效,允许发送数据
RS485_RE为低电平的时候,RE为低电平有效,允许接收数据

485转换芯片可以把输入的串口信号转化成差分信号,也可以差分信号转化成串口信号

信号线A、B

A>B0
B>A1

STM32实现主从机RS485通信

STM32F103ZET6

RS485接口 A接A B接B

image-20230422180220414

从机:

RS485发送 “Hello World”

char Buff[30];
//*******************
while (1){/* USER CODE END WHILE */HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_SET); //拉高发送sprintf(Buff,"Hello World");HAL_UART_Transmit(&huart2, Buff, sizeof(Buff), 0xffff); HAL_Delay(500);/* USER CODE BEGIN 3 */}

主机:

RS485接收 “Hello World”

虚拟串口打印

HAL_UART_Receive_IT(&huart2, RxBuff,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7, GPIO_PIN_RESET);  //拉低接收
//*******************************
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{uint8_t i;if(huart->Instance==USART2){HAL_UART_Receive_IT(&huart2,RxBuff,10);CDC_Transmit_FS(RxBuff, sizeof(RxBuff));}
}

image-20230417170911895

Modbus协议

Modbus就是一种用在工业上的简单协议!

大致分为以下几种:

  • Modbus-RTU
  • Modbus-ASCII
  • Modbus-TCP

以上三种协议,一个设备只会有一种协议,该电机使用的是Modbus-RTU。

Modbus是主从方式通信,也就是说,不能同步进行通信,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。

Modbus Poll主机Modbus Slave从机通信

第三方测试软件:vspd、Modbus Poll、Modbus Slave

虚拟一个串口

image-20230422151542599

image-20230422152049651

修改配置

image-20230422152532666

读取数据

发送

Tx:000024- 01 03 00 00 00 0A C5 CD

010300 0000 0AC5 CD
ID号功能码起始地址数据内容校验码

接收

Rx:000143- 01 03 0A 00 01 00 02 00 01 00 00 00 00 37 26

01030A00 01 00 02 00 01 00 00 00 0037 26
ID号功能码字节长度数据内容校验位

发送单条数据

010600 0400 0388 0A
ID号功能码起始地址数据内容校验码

image-20230422160343498

发送多条数据

主机TX:

ID功能码起始地址寄存器长度数据内容校验码
0116(0X10)00 0000 0510字节XX XX

Tx:001200- 01 10 00 03 00 01 02 00 04 A7 A0

ID功能码起始地址数据长度字节长度数据内容校验码
011000 0300 010200 04A7 A0

功能码:

  • 03->读取数据
  • 06->发送单条数据
  • 10->发送多条数据

SSCOM主机Modbus Slave从机通信

读取数据

image-20230422165736981

发送单条数据

image-20230422165911874

发送多条数据

image-20230422170213418

STM32通过RS485完成Modbus协议通信

移植Modbus协议

mbrtu_master.h

#ifndef MBRTU_MASTER_H_
#define MBRTU_MASTER_H_#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "mbrtu_master.h"
#include "usart.h"
#include "tim.h"/
///
/// MODBUS RTU 主机控制结构
///
///
typedef struct
{//// 收发数据缓存//uint8_t ucBuf[128];//// 收发数据状态//uint16_t usStatus;//// 如果使用了RTOS需要进行互斥,那么需要实现以下两个函数的绑定//void (*lock)(void);void (*unlock)(void);//// 微秒延时函数,用于等待超时//void (*delayms)(uint32_t nms);//// 定时器启动和停止函数//void (*timerStop)(void);void (*timerStart)(void);//// 发送数据函数,可以是串口、TCP等//uint32_t (*sendData)(const void* buf, uint32_t len);//// 以下四个回调函数分别是:读线圈、读离散量输入、读保持寄存器、读输入寄存器//void (*readCoilsCallback)(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfCoilsState, uint16_t usLen);void (*readDiscreteInputsCallback)(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfDiscreteInputsState, uint16_t usLen);void (*readHoldingRegistersCallback)(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusHoldingRegistersVal, uint16_t usLen);void (*readInputRegistersCallback)(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusInputRegistersVal, uint16_t usLen);}MBRTUMaterTypeDef;/
static void timerStop(void);
static void timerStart(void);
static void delayms(uint32_t nms);
static uint32_t sendData(const void *buf, uint32_t len);
static void readCoilsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t *pucBitsOfCoilsState, uint16_t usLen);
static void readDiscreteInputsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t *pucBitsOfDiscreteInputsState, uint16_t usLen);
static void readHoldingRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t *pusHoldingRegistersVal, uint16_t usLen);
static void readInputRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t *pusInputRegistersVal, uint16_t usLen);/
///
/// MODBUS RTU 主机 API
///
///
int MBRTUMasterReadCoils(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout);
int MBRTUMasterReadDiscreteInputs(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout);
int MBRTUMasterReadHoldingRegisters(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout);
int MBRTUMasterReadInputRegisters(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout);
int MBRTUMasterWriteSingleCoil(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint8_t ucState, uint16_t usTimeout);
int MBRTUMasterWriteSingleRegister(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usRegVal, uint16_t usTimeout);
int MBRTUMasterWriteMultipleCoils(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, const uint8_t* pucStateBitsBuf, uint16_t usTimeout);
int MBRTUMasterWriteMultipleRegisters(MBRTUMaterTypeDef* psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, const uint16_t* pusRegVal, uint16_t usTimeout);/
///
/// MODBUS RTU 主机接收数据回调函数和超时回调函数
/// 
/// MBRTUMasterRecvByteISRCallback:放置于串口接收中断中
/// MBRTUMasterTimerISRCallback:放置于定时器超时中断中
///
void MBRTUMasterRecvByteISRCallback(MBRTUMaterTypeDef* psModbus, uint8_t ucByte);
void MBRTUMasterTimerISRCallback(MBRTUMaterTypeDef* psModbus);#endif /* MBRTU_MASTER_H_ */

mbrtu_master.c

/** mbrtu_master.c**  Created on: 2022年4月29日*      Author: hello*/#include "mbrtu_master.h"static uint16_t usMBCRC16(uint8_t *pucFrame, uint16_t usLen)
{static const uint8_t aucCRCHi[] ={0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40};static const uint8_t aucCRCLo[] ={0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,0x41, 0x81, 0x80, 0x40};uint8_t ucCRCHi = 0xFF;uint8_t ucCRCLo = 0xFF;int iIndex;while (usLen--){iIndex = ucCRCLo ^ *(pucFrame++);ucCRCLo = (uint8_t)(ucCRCHi ^ aucCRCHi[iIndex]);ucCRCHi = aucCRCLo[iIndex];}return (uint16_t)(ucCRCHi << 8 | ucCRCLo);
}//读取数据
static uint32_t MBRTUMasterRead(MBRTUMaterTypeDef *pMaster, uint8_t ucSlaveAddr, uint8_t ucCmd, uint16_t usStartAddr, uint16_t usNum)
{uint16_t crc;pMaster->ucBuf[0] = ucSlaveAddr;pMaster->ucBuf[1] = ucCmd;pMaster->ucBuf[2] = ((usStartAddr & 0XFF00) >> 8);pMaster->ucBuf[3] = (usStartAddr & 0XFF);pMaster->ucBuf[4] = ((usNum & 0XFF00) >> 8);pMaster->ucBuf[5] = (usNum & 0XFF);crc = usMBCRC16((uint8_t *)pMaster->ucBuf, 6);pMaster->ucBuf[6] = (uint8_t)(crc & 0xFF);pMaster->ucBuf[7] = (uint8_t)(crc >> 8);return pMaster->sendData(pMaster->ucBuf, 8);
}/*** 主机读取线圈状态* @param  ucSlaveAddress 从机地址* @param  usAddress      要读取的线圈起始地址* @param  usCmd          0x01* @param  usNum          要读取的线圈数量* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterReadCoils(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout)
{int ret = -1;int delay;if (psModbus->lock != NULL){psModbus->lock();}psModbus->usStatus = 0;MBRTUMasterRead(psModbus, ucSlaveAddress, 0X01, usAddress, usNum);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X01){psModbus->readCoilsCallback(usAddress, usNum, &psModbus->ucBuf[3], psModbus->ucBuf[2]);ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机读取离散量输入* @param  ucSlaveAddress 从机地址* @param  usAddress      要读取的离散量起始地址* @param  usCmd          0x02* @param  usNum          要读取的离散量数量* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterReadDiscreteInputs(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout)
{int ret = -1;int delay;if (psModbus->lock != NULL){psModbus->lock();}psModbus->usStatus = 0;MBRTUMasterRead(psModbus, ucSlaveAddress, 0X02, usAddress, usNum);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X02){psModbus->readDiscreteInputsCallback(usAddress, usNum, &psModbus->ucBuf[3], psModbus->ucBuf[2]);ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机读取保持寄存器!!!!!!!!!!!!* @param  ucSlaveAddress 从机地址* @param  usAddress      要读取的保持寄存器起始地址* @param  usCmd          0x03* @param  usNum          要读取的保持寄存器数量* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterReadHoldingRegisters(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout)
{int ret = -1;int delay;if (psModbus->lock != NULL){psModbus->lock();}psModbus->usStatus = 0;MBRTUMasterRead(psModbus, ucSlaveAddress, 0X03, usAddress, usNum);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X03){psModbus->readHoldingRegistersCallback(usAddress, usNum, (const uint16_t *)&psModbus->ucBuf[3], psModbus->ucBuf[2] >> 1);ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机读取输入寄存器* @param  ucSlaveAddress 从机地址* @param  usAddress      要读取的输入寄存器起始地址* @param  usCmd          0x04* @param  usNum          要读取的输入寄存器数量* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterReadInputRegisters(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, uint16_t usTimeout)
{int ret = -1;int delay;if (psModbus->lock != NULL){psModbus->lock();}psModbus->usStatus = 0;MBRTUMasterRead(psModbus, ucSlaveAddress, 0X04, usAddress, usNum);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X04){psModbus->readInputRegistersCallback(usAddress, usNum, (const uint16_t *)&psModbus->ucBuf[3], psModbus->ucBuf[2] >> 1);ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机写单个线圈* @param  ucSlaveAddress 从机地址* @param  usAddress      线圈地址* @param  usCmd          0x05* @param  ucState        要设置的线圈状态,1或者0* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/int MBRTUMasterWriteSingleCoil(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint8_t ucState, uint16_t usTimeout)
{int ret = -1;int delay;uint16_t crc;if (psModbus->lock != NULL){psModbus->lock();}psModbus->ucBuf[0] = ucSlaveAddress;psModbus->ucBuf[1] = 0X05;psModbus->ucBuf[2] = usAddress & 0XFF;psModbus->ucBuf[3] = usAddress >> 8;psModbus->ucBuf[4] = ucState ? 0XFF : 0X00;psModbus->ucBuf[5] = 0X00;crc = usMBCRC16((uint8_t *)psModbus->ucBuf, 6);psModbus->ucBuf[6] = (uint8_t)(crc & 0xFF);psModbus->ucBuf[7] = (uint8_t)(crc >> 8);psModbus->usStatus = 0;psModbus->sendData(psModbus->ucBuf, 8);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X05){ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机写单个寄存器!!!!!!!!!!!* @param  ucSlaveAddress 从机地址* @param  usAddress      寄存器地址* @param  usCmd          0x06* @param  usRegVal       寄存器值* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
//	MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, RUN_MODE_ADDR, 0x0003, 100);
int MBRTUMasterWriteSingleRegister(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usRegVal, uint16_t usTimeout)
{int ret = -1;int delay;uint16_t crc;if (psModbus->lock != NULL){psModbus->lock();}psModbus->ucBuf[0] = ucSlaveAddress;psModbus->ucBuf[1] = 0X06;psModbus->ucBuf[2] = usAddress & 0XFF;psModbus->ucBuf[3] = usAddress >> 8;psModbus->ucBuf[4] = usRegVal >> 8;psModbus->ucBuf[5] = usRegVal & 0XFF;crc = usMBCRC16((uint8_t *)psModbus->ucBuf, 6);psModbus->ucBuf[6] = (uint8_t)(crc & 0xFF);psModbus->ucBuf[7] = (uint8_t)(crc >> 8);psModbus->usStatus = 0;psModbus->sendData(psModbus->ucBuf, 8);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X06){ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机写多个线圈状态* @param  ucSlaveAddress  从机地址* @param  usAddress       线圈起始地址* @param  usNum           要写的线圈数量* @param  pucStateBitsBuf 存放线圈状态,1比特代表一个线圈状态* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterWriteMultipleCoils(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, const uint8_t *pucStateBitsBuf, uint16_t usTimeout)
{int ret = -1;int delay;uint16_t crc;uint16_t usIndex = 0, usBytes = 0;if (psModbus->lock != NULL){psModbus->lock();}psModbus->ucBuf[usIndex++] = ucSlaveAddress;psModbus->ucBuf[usIndex++] = 0X0F;psModbus->ucBuf[usIndex++] = usAddress & 0XFF;psModbus->ucBuf[usIndex++] = usAddress >> 8;psModbus->ucBuf[usIndex++] = usNum >> 8;psModbus->ucBuf[usIndex++] = usNum & 0XFF;usBytes = (usNum - 1) / 8 + 1;psModbus->ucBuf[usIndex++] = usBytes;while (usBytes--){psModbus->ucBuf[usIndex++] = *pucStateBitsBuf++;}crc = usMBCRC16((uint8_t *)psModbus->ucBuf, usIndex);psModbus->ucBuf[usIndex++] = (uint8_t)(crc & 0xFF);psModbus->ucBuf[usIndex++] = (uint8_t)(crc >> 8);psModbus->usStatus = 0;psModbus->sendData(psModbus->ucBuf, usIndex);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X0F){ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}/*** 主机写多个寄存器!!!!!!!!!!!* @param  ucSlaveAddress 从机地址* @param  usAddress      要写的寄存器起始地址* @param  usCmd          0x10* @param  usNum          要写的寄存器数量* @param  pusRegVal      存放要写的寄存器值* @param  usTimeout      超时时间,单位毫秒* @return                0:成功  <0:执行失败*/
int MBRTUMasterWriteMultipleRegisters(MBRTUMaterTypeDef *psModbus, uint8_t ucSlaveAddress, uint16_t usAddress, uint16_t usNum, const uint16_t *pusRegVal, uint16_t usTimeout)
{int ret = -1;int delay;uint16_t crc;uint16_t usIndex = 0;if (psModbus->lock != NULL){psModbus->lock();}psModbus->ucBuf[usIndex++] = ucSlaveAddress;psModbus->ucBuf[usIndex++] = 0X10;psModbus->ucBuf[usIndex++] = usAddress & 0XFF;psModbus->ucBuf[usIndex++] = usAddress >> 8;psModbus->ucBuf[usIndex++] = usNum >> 8;psModbus->ucBuf[usIndex++] = usNum & 0XFF;psModbus->ucBuf[usIndex++] = usNum << 1;while (usNum--){psModbus->ucBuf[usIndex++] = *pusRegVal >> 8;psModbus->ucBuf[usIndex++] = *pusRegVal & 0XFF;pusRegVal++;}crc = usMBCRC16((uint8_t *)psModbus->ucBuf, usIndex);psModbus->ucBuf[usIndex++] = (uint8_t)(crc & 0xFF);psModbus->ucBuf[usIndex++] = (uint8_t)(crc >> 8);psModbus->usStatus = 0;psModbus->sendData(psModbus->ucBuf, usIndex);while (usTimeout != 0){if (psModbus->usStatus & 0X8000){if (psModbus->ucBuf[0] == ucSlaveAddress && psModbus->ucBuf[1] == 0X10){ret = 0;}else{ret = -2;}psModbus->usStatus = 0;break;}delay = usTimeout > 5 ? 5 : usTimeout;usTimeout -= delay;psModbus->delayms(delay);}if (psModbus->unlock != NULL){psModbus->unlock();}return ret;
}void MBRTUMasterRecvByteISRCallback(MBRTUMaterTypeDef *psModbus, uint8_t ucByte)
{psModbus->timerStop();if (psModbus->usStatus < sizeof(psModbus->ucBuf)){psModbus->ucBuf[psModbus->usStatus++] = ucByte;psModbus->timerStart();}else{psModbus->usStatus |= 0X8000;}
}void MBRTUMasterTimerISRCallback(MBRTUMaterTypeDef *psModbus)
{psModbus->timerStop();psModbus->usStatus |= 0X8000;
}#ifdef USE_RTOSstatic void mutex_lock(void)
{
}static void mutex_unlock(void)
{
}#endifstatic void timerStop(void)
{HAL_TIM_Base_Stop_IT(&htim3);
}static void timerStart(void)
{__HAL_TIM_SET_COUNTER(&htim3, 0);HAL_TIM_Base_Start_IT(&htim3);
}static void delayms(uint32_t nms)
{HAL_Delay(nms);
}static uint32_t sendData(const void *buf, uint32_t len)
{if (HAL_UART_Transmit(&huart2, (uint8_t *)buf, len, 100) != HAL_OK){len = 0;}return len;
}static void readCoilsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t *pucBitsOfCoilsState, uint16_t usLen)
{uint8_t ucLoops = (usNum - 1) / 8 + 1;uint8_t ucState, ucBits;printf(" Read %d coils starting at start address %d: ", usNum, usStartAddr);while (ucLoops != 0){ucState = *pucBitsOfCoilsState++;ucBits = 0;while (usNum != 0 && ucBits < 8){printf("%d ", ucState & 0X01 ? 1 : 0);ucState >>= 1;usNum--;ucBits++;}ucLoops--;}printf("\r\n");
}static void readDiscreteInputsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t *pucBitsOfDiscreteInputsState, uint16_t usLen)
{uint8_t ucLoops = (usNum - 1) / 8 + 1;uint8_t ucState, ucBits;printf(" Read %d discrete inputs starting at start address %d: ", usNum, usStartAddr);while (ucLoops != 0){ucState = *pucBitsOfDiscreteInputsState++;ucBits = 0;while (usNum != 0 && ucBits < 8){printf("%d ", ucState & 0X01 ? 1 : 0);ucState >>= 1;usNum--;ucBits++;}ucLoops--;}printf("\r\n");
}static void readHoldingRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t *pusHoldingRegistersVal, uint16_t usLen)
{uint16_t val;printf(" Read %d hold registers starting at start address %d: ", usNum, usStartAddr);while (usLen--){val = *pusHoldingRegistersVal++;val = ((val & 0X00FF) << 8) | ((val & 0XFF00) >> 8); // 转换大小端printf("%04X ", val);}printf("\r\n");
}static void readInputRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t *pusInputRegistersVal, uint16_t usLen)
{uint16_t val;printf(" Read %d input registers starting at start address %d: ", usNum, usStartAddr);while (usLen--){val = *pusInputRegistersVal++;val = ((val & 0X00FF) << 8) | ((val & 0XFF00) >> 8); // 转换大小端printf("%04X ", val);}printf("\r\n");
}MBRTUMaterTypeDef MBRTUHandle ={.delayms = delayms,.timerStart = timerStart,.timerStop = timerStop,.sendData = sendData,.readCoilsCallback = readCoilsCallback,.readDiscreteInputsCallback = readDiscreteInputsCallback,.readHoldingRegistersCallback = readHoldingRegistersCallback,.readInputRegistersCallback = readInputRegistersCallback,#ifdef USE_RTOS // 使用了RTOS那么需要实现互斥.lock = mutex_lock,.unlock = mutex_unlock,
#endif};

main.c

TX

extern MBRTUMaterTypeDef MBRTUHandle;
void main(){
/*.............*/while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_SET); //拉高发送MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 6, 0X0501, 100);HAL_Delay(1000);}}

STM32主机Modbus Slave从机通信

#include "mbrtu_master.h"extern MBRTUMaterTypeDef MBRTUHandle;
for(j=0; j<=255;j++) {for(i=0;i<=8;i++) {MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, i, j, 100);HAL_Delay(1000);}
}

STM32按键控制发送ZLAC8015D电机指令

      if (HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == 1) {/*延时一小段时间,消除抖动*/HAL_Delay(10);/*延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下*/if (HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == 1) {/*等待按键弹开才退出按键扫描函数*/while (HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == 1);MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X200D, 0x0003, 100);//                MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X2088, 0X0064, 100);}}if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0) {/*延时一小段时间,消除抖动*/HAL_Delay(10);/*延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下*/if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0) {/*等待按键弹开才退出按键扫描函数*/while (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X200E, 0x0008, 100);HAL_Delay(10);MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X200E, 0x0010, 100);}}if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0) {/*延时一小段时间,消除抖动*/HAL_Delay(10);/*延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下*/if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0) {/*等待按键弹开才退出按键扫描函数*/while (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0);MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X2088, 0X0064, 100);
//                HAL_Delay(10);MBRTUMasterWriteSingleRegister(&MBRTUHandle, 1, 0X2089, 0X0064, 100);HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);}}}

指令依次是

设置速度模式

使能

电机同步启动

设置左电机目标转速100RPM

设置右点击目标转速100RPM

image-20230430183058256

image-20230429183327552

相关文章:

STM32HAL库RS485-ModBus协议控制伺服电机

STM32HAL库RS485-ModBus协议控制伺服电机 一个月前&#xff0c;接手了一个学长的毕设小车&#xff0c;小车采用rs485通信的modbus协议驱动轮毂电机&#xff0c;与往常我学习的pwm控制电机方法大相径庭&#xff0c;在这里以这篇博客记录下该学习过程。 小车主要架构 电机型号 …...

【医学图像】图像分割系列.3 (uncertainty)

介绍几篇使用不确定性引导的医学图像分割论文&#xff1a;UA-MT&#xff08;MICCAI2019&#xff09;&#xff0c;SSL4MIS&#xff08;MICCAI2021&#xff09;&#xff0c;UG-MCL&#xff08;AIIM2022&#xff09;. Uncertainty-aware Self-ensembling Model for Semi-supervise…...

Java有线程安全的set吗?

在Java中&#xff0c;有线程安全的Set实现。一个常用的线程安全的Set实现是ConcurrentSkipListSet。ConcurrentSkipListSet是一个有序的集合&#xff0c;基于跳表(SkipList)的数据结构实现。它提供了线程安全的操作&#xff0c;并且具有较好的性能。 接下来笔者用一段简单的Jav…...

《HelloGitHub》第 86 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …...

LDGRB-01 3BSE013177R1 将数字输入和继电器输出结合

LDGRB-01 3BSE013177R1包的一部分是全面的通信选项&#xff0c;包括Modbus主/从或CS31&#xff0c;这种产品很少提供。128kB的用户内存和0.1秒/指令的程序处理时间只是AC500-eCo令人印象深刻的性能的两个例子。除了与现有AC500系列的互操作性&#xff0c;AC500-eCo系统还使用基…...

手动计算校正年龄、性别后的标准化死亡率 (SMR)

分析队列人群有无死亡人数超额&#xff0c;通常应用标准人群死亡率来校正&#xff0c;即刻观察到中的实际死亡数&#xff08;D&#xff09;与定一个标准的死亡人数&#xff08;E&#xff09;&#xff0c;D与E之比称为死亡比&#xff08;standarized Mortality ratio&#xff0c…...

Java组合模式:构建多层次公司组织架构

在现实生活中&#xff0c;常常会遇到用树形结构组织的一些场景&#xff0c;比如国家省市&#xff0c;学校班级&#xff0c;文件目录&#xff0c;分级导航菜单&#xff0c;以及典型的公司组织架构&#xff0c;整个层次结构自顶向下呈现一颗倒置的树。这种树形结构在面向对象的世…...

Langchain-ChatGLM:基于本地知识库问答

文章目录 ChatGLM与Langchain简介ChatGLM-6B简介ChatGLM-6B是什么ChatGLM-6B具备的能力ChatGLM-6B具备的应用 Langchain简介Langchain是什么Langchain的核心模块Langchain的应用场景 ChatGLM与Langchain项目介绍知识库问答实现步骤ChatGLM与Langchain项目特点 项目主体结构项目…...

设计模式十 适配器模式

适配器模式 适配器模式是一种结构型设计模式。作用&#xff1a;当接口无法和类匹配到一起工作时&#xff0c;通过适配器将接口变换成可以和类匹配到一起的接口。&#xff08;注&#xff1a;适配器模式主要解决接口兼容性问题&#xff09; 适配器的优点与缺点&#xff1a; 优…...

1.6 初探JdbcTemplate操作

一、JdbcTemplate案例演示 1、创建数据库与表 &#xff08;1&#xff09;创建数据库 执行命令&#xff1a;CREATE DATABASE simonshop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 或者利用菜单方式创建数据库 - simonshop 打开数据库simonshop &#x…...

为什么要用线程池?

线程池是一种管理和复用线程资源的机制&#xff0c;它由一个线程池管理器和一组工作线程组成。线程池管理器负责创建和销毁线程池&#xff0c;以及管理线程池中的工作线程。工作线程则负责执行具体的任务。 线程池的主要作用是管理和复用线程资源&#xff0c;避免了线程的频繁…...

c语言的预处理和编译

预处理 文件包含 当预处理器发现#include指令时&#xff0c;会查看后面的文件名并把文件的内容包含到当前文件中 两种写法 尖括号&#xff1a;引用的是编译器的库路径里面的头文件。 双引号&#xff1a;引用的是程序目录中相对路径中的头文件&#xff0c;如果找不到再去上面…...

网络安全必学 SQL 注入

1.1 .Sql 注入攻击原理 SQL 注入漏洞可以说是在企业运营中会遇到的最具破坏性的漏洞之一&#xff0c;它也是目前被利用得最多的漏洞。要学会如何防御 SQL 注入&#xff0c;首先我们要学习它的原理。 针对 SQL 注入的攻击行为可描述为通过在用户可控参数中注入 SQL 语法&#x…...

Docker基础知识详解

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;文章 &#x1f96d;本文内容&am…...

腾讯、阿里入选首批“双柜台证券”,港股市场迎盛夏升温?

6月5日&#xff0c;香港交易所发布公告&#xff0c;将于6月19日在香港证券市场推出“港币&#xff0d;人民币双柜台模式”&#xff0c;当日确定有21只证券指定为双柜台证券。同时&#xff0c;港交所还表示&#xff0c;在双柜台模式推出前&#xff0c;更多证券或会被接纳并加入双…...

CentOS7 使用Docker 安装MySQL

CentOS7 使用Docker 安装MySQL Docker的相关知识本篇不会再概述&#xff0c;有疑惑的同学请自行查找相关知识。本篇只是介绍如何在CentOS7下使用Docker安装相应的镜像。 可登陆Docker官网 https://docs.docker.com 之后可以跟着官方的步骤进行安装。 clipboard.png 具体安装过…...

注解和反射复习

注解 注解&#xff1a;给程序和人看的&#xff0c;被程序读取&#xff0c;jdk5.0引用 内置注解 override:修饰方法&#xff0c;方法声明和重写父类方法&#xff0c; Deprecated:修饰&#xff0c;不推荐使用 suppressWarnings用来抑制编译时的警告,必须添加一个或多个参数s…...

RocketMQ的demo代码

下面是一个使用Java实现的RocketMQ示例代码&#xff0c;用于发送和消费消息&#xff1a; 首先&#xff0c;您需要下载并安装RocketMQ&#xff0c;并启动NameServer和Broker。 接下来&#xff0c;您可以使用以下示例代码来发送和消费消息&#xff1a; Producer.java文件&…...

C++ 连接、操作postgreSQL(基于libpq库)

C++ 连接postgreSQL(基于libpq库) 1.环境2.数据库操作2.1. c++ 连接数据库2.2. c++ 删除数据库属性表内容2.3. c++ 插入数据库属性表内容2.4 c++ 关闭数据库1.环境 使用libpq库来链接postgresql数据库,主要用到的头文件是这个: #include "libpq-fe.h"2.数据库操…...

Node.js技术简介及其在Web开发中的应用

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境&#xff0c;使得JavaScript能够在服务器端运行。Node.js采用事件驱动、非阻塞I/O模型&#xff0c;能够处理大量并发请求&#xff0c;非常适合处理I/O密集型的应用程序。本文将介绍Node.js的特点、优势以及在Web开发中的应…...

时间序列分析:原理与MATLAB实现

2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 目录 1. 时间序列分析简介 2. 自回归模型(AR) 2.1. 参数估计 2.2. MATLAB实现...

mysql排序之if(isnull(字段名),0,1),字段名 或者 if(isnull(字段名),1,0),字段名

mysql排序之if(isnull(字段名),0,1),字段名 或者 if(isnull(字段名),1,0),字段名 默认情况下&#xff0c;MySQL将null算作最小值。如果想要手动指定null的顺序&#xff0c;可以这样处理&#xff1a; 将null强制放在最前 //null, null, 1,2,3,4&#xff08;默认就是这样&#…...

华为OD机试真题 Java 实现【递增字符串】【2023Q1 200分】,附详细解题思路

一、题目描述 定义字符串完全由“A’和B"组成,当然也可以全是"A"或全是"B。如果字符串从前往后都是以字典序排列的,那么我们称之为严格递增字符串。 给出一个字符串5,允许修改字符串中的任意字符,即可以将任何的"A"修改成"B,也可以将…...

合并文件解决HiveServer2内存溢出方案

一、文件过多导致HiveServer2内存溢出 1.1查看表文件个数 desc formatted yanyu.tmp• 表文件数量为6522102 1.2查看表文件信息 hadoop fs -ls warehouse/yanyu.db/tmp• 分区为string 类型的time字段&#xff0c;分了2001个区。 1.3.查看某个分区下的文件个数为10000个 …...

韧性数据安全体系缘起与三个目标 |CEO专栏

今年4月&#xff0c;美创科技在数据安全领域的新探索——“韧性”数据安全防护体系框架正式发布亮相。 为帮您更深入了解“韧性数据安全”&#xff0c;我们特别推出专栏“构建适应性进化的韧性数据安全体系”&#xff0c;CEO柳遵梁亲自执笔&#xff0c;进行系列解读分享。 首期…...

华为OD机试真题 Java 实现【火车进站】【牛客练习题】

一、题目描述 给定一个正整数N代表火车数量,0<N<10,接下来输入火车入站的序列,一共N辆火车,每辆火车以数字1-9编号,火车站只有一个方向进出,同时停靠在火车站的列车中,只有后进站的出站了,先进站的才能出站。 要求输出所有火车出站的方案,以字典序排序输出。 …...

c#快速入门(下)

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb;Inline和lambda委托和lambda &#x1f449;&#x1f…...

基于深度学习的目标姿态检测方法_kaic

目录 摘要 第1章 引言 1.1 研究背景和意义 1.2 国内外研究现状 1.3 主要内容 第2章 单目相机的目标姿态检测技术 2.1单目相机的工作原理 2.2目标姿态检测 2.3已有的目标姿态检测方法及其局限性 2.4本章总结 第3章 构建数据集 3.1 数据集来源 3.2数据集标注 3.3数据集分析 3.4本…...

Pycharm设置Python每个文件开头自定义模板(带上声明字符编码、作者名、时间等)

Pycharm设置地址&#xff1a; 在File---settings---Editor---File and Code Templates---Python script 脚本里添加: 模板声明设置参考&#xff1a; # ---encoding:utf-8--- # Time : ${DATE} ${HOUR}:${MINUTE} # Author : 作者名 # Email &#xff1a;你的邮箱 # Sit…...

Gem相关操作命令

Gem相关操作命令 gem -v # 查看 gem 版本gem source # 查看 gem 配置源 gem source -l # 查看 gem 配置源目录 gem sources -a url # 添加 gem 配置源&#xff08;url 需换成网址&#xff09; gem sources --add url # 添加 gem 配置源&#xff08;url 需换成网址&#xff09;…...