MODBUS transplantation STM32, STM32 faire esclave

journal d'apprentissage MODBUS

A, protocole MODBUS

1, le protocole de communication

  1. protocole de couche de matériel: des problèmes de transmission, l'équivalent de la route
  2. Protocole de communication série: RS232, RS485, bus CAN

1.1, trois mode de communication

1.1.1, mode simplex (recto)

Prend en charge que de transmission de signaux de communication simplex (avant ou arrière) dans une direction à tout moment ne peut pas changer la direction de transmission du signal. Pour assurer une transmission correcte des signaux de données, le récepteur doit vérifier les données reçues, si la vérification d'erreur, puis demande de retransmission de signal est transmis à travers le canal de surveillance. Une telle méthode est applicable à un système de collecte de données, telles que la collecte, les données météorologiques calcul centralisé des frais de téléphone. Par exemple, la communication entre l'ordinateur et l'imprimante sont en mode recto, depuis l'ordinateur des données transmettent uniquement à l'imprimante, mais pas dans le sens opposé de la transmission de données. Il y a des canaux de communication, comme une transmission radio simplex et similaires.

1.1.2, le mode half-duplex (logiciel nécessaire de faire le protocole supérieur) (semi-duplex)

Semi-duplex signal d'autorisation de communication transmis dans les deux sens, mais permet seulement un signal à la fois dans un canal de transmission unidirectionnelle. Par conséquent, la moitié de communication duplex est en fait une direction commutable de communication simplex. Tel mode applique aux demandes de renseignements, la récupération, le système de communication de données de calcul scientifique; walkie-talkie est une communication en semi-duplex classique. Comme transmission walkie-talkie et la réception en utilisant la même fréquence, en même temps, ne sont pas autorisés. Ainsi, après la partie terminée, la nécessité d'essayer d'informer l'autre partie avait fini de parler (par exemple, après fini plus « plus »), l'autre partie de savoir que vous pouvez commencer à parler.

1.1.3, le mode full-duplex (full-duplex)

communication en duplex intégral permet un transfert simultané de données dans les deux directions, à savoir il y a deux canaux, ce qui permet la transmission bidirectionnelle simultanée. communication en duplex intégral est une combinaison de deux système de communication simplex, il faut envoyer et recevoir des côtés séparés et ont de recevoir la capacité de transmission. Haut rendement de communication en duplex intégral, le contrôle est simple, mais le coût élevé. La communication entre les ordinateurs sont en duplex intégral. téléphone général, téléphone cellulaire est un système duplex complet, car on peut entendre dans son discours de voix de chacun.

Référence Liens: https://blog.csdn.net/iningwei/article/details/100134783

1.2, du mode maître:

Mode maître-esclave, modèle de conception de base de données est la plus courante et surtout un modèle utilisé dans le travail de conception de tous les jours, qui décrit le tableau principal de la relation entre les deux, est un « un à plusieurs » typique relation.
exigences:

  1. 系统中只有一个设备时主机
  2. 系统中的所有从机不可以主动向主机发数据
  3. 系统中的主机和所有从机上电后都处于监听状态
  4. 任何一次的数据交换都要由主机发起
     4.1、将自己转为发送状态
     4.2、主机按照预先约定的格式,发出寻址数据帧
     4.3、恢复自己的接受状态,等待所寻址的从机响应

1.3, les protocoles de couche logicielle: Résolution objet du transfert

1.3.1, le mode maître-esclave
  1. L'ensemble du système, un seul hôte, chaque dispositif esclave doit avoir une adresse unique (0 à 247)

  2. Où l'adresse de diffusion des bits d'adresse 0: 0 adresse de l'hôte à l'appareil envoie un paquet, le paquet est mis à tous les dispositifs esclaves. 0 adresse de paquet tous les esclaves ne répondent pas.

2, l'adresse hôte du format de trame MODBUS

>MODBUS的两种传输方式:RTU方式和ASC方式
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>ASC方式:0x03  {发送0 :0x30:0011 0000 }{ 发送3:0x33:0011 0011}
>
>所以ASC的通信效率低,但是方便调试,使用实验;工业上都采用RTU方式,效率高

2.3, mode RTU

1, 2 adresse de l'esclave, le code de fonction (127) 3, les données 1 à 4, les données n check code (CRCL, CRCH)

Où: 1-3 participation de contrôle CRC16

Réception de données à partir d'un temps d'arrêt de la machine est plus de 3,5 octets, alors la trame complète qui adresse de l'hôte, et commence à traiter.

Par exemple: Vitesse: 9600bt / s

Par conséquent, chaque fois que les données de transmission T = 1000000us / 9600 = 104us

Un temps d'octet de bit = 10T = 1004us (arrêt du bit de départ bit 8) (format série)

Donc, le temps était: 3,5 * 10T = 3645us

2.4, chemin ASC

1, 2, 3 adresse, le code de fonction 4, les données 1 à données n 5, (adresse de données) en utilisant la somme de contrôle LRC = ((+ adresses + données 1 Code de fonction Données n -)% 256) + 1 = (0 255) 6,13 10 (retour chariot)

2.5, bref CRC

1. Le registre est chargé avec un renvoi au registre CRC. FFFF en hexadécimal 16 bits (toutes les).
2. L'octet de poids faible du message avec le premier octet de 8 bits à 16 bits CRC registre XOR ., CRC résultat est placé dans le registre
registre 3. CRC droite de 1 bit (LSB direction), la réduction à zéro le MSB LSB est extrait et détecté ..
4. (Si le LSB est 0): répéter l' étape 3 (un autre changement).
(Si le LSB est 1): la valeur polynomiale registre CRC XOR 0xA001 (1010 0000 0000 0001).
5. Répétez les étapes 3 et 4 jusqu'à huit quarts de travail. Lorsque vous avez terminé, nous allons terminer l'opération complète d'octets de 8 bits.
6. Répétez les étapes pour le prochain paquet 2-5 octets, cette opération se poursuit jusqu'à ce que tous les paquets sont traités.
7.CRC le contenu du registre de dernière valeur de CRC.
8. Lorsque la valeur CRC est placé au moment du paquet, octet de poids faible doit être échangé.

3, les données de réponse de l'appareil à partir du format de paquet

  1. paquets Echo et hôte format de paquet requête est le même paquet
  2. Lorsque la réponse normale, compatible avec l'ordinateur central envoie le code de fonction de code de fonction (1 à 127)
  3. réponse anormale, par exemple, le code de fonction 128 pour ajouter le code de fonction reçue sur la base de: cheveux 0x03 revenu: 0x03 +128

4, le protocole MODBUS implémenté

  1. Avec le matériel de port série
  2. Besoin d'horloge (précis à un millième de seconde) matériel

Deux processus de transplantation MODBUS STM32

1, la conception du processus d'initialisation du système

开始
配置系统时钟为72MHZ
配置基本定时器为1MS
配置串口为9600bts,并开启接受中断
使能定时器和串口中断,串口中断优先级>定时器中断优先级

1.1, l'horloge du système

SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ

1,2, une étape disposé sensiblement temporisateur

void BASIC_TIM_Config(void)
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);      //开启定时器时钟,即内部时钟CK_INT=72M
  TIM_TimeBaseStructure.TIM_Period=TIM6_Period;        //自动重装载寄存器周的值(计数值)
  // 累计TIM_Period 个频率后产生一个更新或者中断
  // 时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M
  TIM_TimeBaseStructure.TIM_Prescaler= TIM6_Prescaler;
 //TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;     // 时钟分频因子 ,基本定时器没有,不用管
 //TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
 //TIM_TimeBaseStructure.TIM_RepetitionCounter=0;            // 重复计数器的值,基本定时器没有,不用管
 TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);        // 初始化定时器
 TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);                  // 清除计数器中断标志位
 TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);               // 开启计数器中断
 TIM_Cmd(BASIC_TIM, ENABLE);                                 // 使能计数器
 //BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, DISABLE);          // 暂时关闭定时器的时钟,等待使用
 }

fichier d'en-tête de base minuterie

#ifdef  BASIC_TIM6                                           // 使用基本定时器TIM6
#define BASIC_TIM                   TIM6
#define BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define BASIC_TIM_IRQ               TIM6_IRQn
#define BASIC_TIM_IRQHandler        TIM6_IRQHandler
#define TIM6_Period                 (1000)
#define TIM6_Prescaler              (72-1)

Fonction de minuterie d'interruption

void BASIC_TIM_IRQHandler (void)                             //定时器中断函数
{
	if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
		{
	   TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
		}
}

Configuration de la minuterie d'interruption permettent

void ALL_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);             // 设置中断组为1
	
	NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;         // 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    // 设置主优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;           // 设置抢占优先级为3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);C
}

La principale structure du programme

int main(void)
{ 

	SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
	BASIC_TIM_Config();  //定时器配置为1MS
     ALL_NVIC_Init();     //配置中断优先级
	
}

Exécutez le programme, afin de déterminer si le point d' arrêt minuterie d' interruption de minuterie temps 1MS

1.3, configurer le port série GPIO

void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);	  // 打开串口GPIO 的时钟	
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);	          // 打开串口外设的时钟	
	// 将USART1 Tx 的GPIO 配置为推挽复用模式
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);		
	// 将USART Rx 的GPIO 配置为浮空输入模式
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);	
	// 配置串口的工作参数
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;	  // 配置波特率
	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(DEBUG_USART, &USART_InitStructure);	             // 完成串口的初始化配置	
	USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE);	         // 使能串口接收中断
	USART_Cmd(DEBUG_USART, ENABLE);	                             // 使能串口
}

tête de série

// 串口2-USART1
#define DEBUG_USART                            USART2
#define DEBUG_USART_CLK                        RCC_APB1Periph_USART2
#define DEBUG_USART_APBxClkCmd                 RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE                   9600
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK                   RCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmd            RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT               GPIOA
#define DEBUG_USART_TX_GPIO_PIN                GPIO_Pin_2
#define DEBUG_USART_RX_GPIO_PORT               GPIOA
#define DEBUG_USART_RX_GPIO_PIN                GPIO_Pin_3
// USART GPIO 中断
#define DEBUG_USART_IRQ                        USART2_IRQn
#define DEBUG_USART_IRQHandler                 USART2_IRQHandler

Port série fonction d'interruption

  void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;
  if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET)  //判断是否有数据接收
	{
		  ucTemp = USART_ReceiveData( DEBUG_USART ); //将接收的一个字节保存
		  USART_SendData(DEBUG_USART,ucTemp);        //保存后发送调试助手,		
  }
}

Port série Interruption Activer la fonction

void ALL_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);             // 设置中断组为1
    NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ ;      // 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   // 设置主优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;          // 设置抢占优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}

La structure principale de la fonction

int main(void)
{ 
	SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
    USART_Config();	
    ALL_NVIC_Init();
}

Exécutez le programme, afin de déterminer si le port série des points d' arrêt d' interruption configuration complète du port série

1.4, configurez le port série agissant sur la minuterie

Lorsque vous avez terminé la réception de données série, une minuterie démarre le temps de comptage> processus de données démarre lorsque 8T

MODBUS besoin de paramètres de la configuration, comme suit

typedef struct
{
 unsigned char   myadd;          //本设备的地址
 unsigned char   rcbuf[100];     //MODBUS接收缓冲区
 unsigned int    timout;         //MODbus的数据断续时间	
 unsigned char   recount;        //MODbus端口已经收到的数据个数
 unsigned char   timrun;         //MODbus定时器是否计时的标志
 unsigned char   reflag;         //收到一帧数据的标志
 unsigned char   Sendbuf[100];   //MODbus发送缓冲区	
}MODBUS;
extern MODBUS modbus;            //声明全局变量,然后在C文件中调用

configuration d'interruption série est la suivante

 void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;
  if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET)  //判断是否有数据接收
	{
	      ucTemp = USART_ReceiveData( DEBUG_USART ); //将接收的一个字节保存
		  modbus.rcbuf[modbus.recount++]=ucTemp;     //保存到MODBUS的接收缓存区	
		  modbus.timout=0;		  //串口接收数据的过程中,定时器不计时	
		  if(modbus.recount==1)    //收到主机发来的一帧数据的第一字节
			  {
			    modbus.timrun=1;   //启动定时
			  }
  }
}

Minuterie d'interruption configuration est la suivante

void BASIC_TIM_IRQHandler (void)     //定时器中断函数
{
	if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
		{
	    if(modbus.timrun!=0)          //串口发送数据是否结束,结束就让定时器定时
		  {
	     modbus.timout++;             //定时器定时1毫秒,并开始记时
			 if(modbus.timout>=8)    //间隔时间达到了时间,假设为8T,实际3.5T即可
				{
					modbus.timrun=0;//关闭定时器--停止定时
					modbus.reflag=1;//收到一帧数据,开始处理数据
				}
			}
	   TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
		}
}

Exécuter le programme détermine si le produit de traitement à la minuterie modbus.reflag = 1;

1.5, la configuration programme de traitement de paquets

void Mosbus_Event(void)
{
	u16 crc;
	u16 rccrc;
  if(modbus.reflag==0)      //没有收到MODbus的数据包
	{
	  return ;              //没有收到处理指令,继续等待下一条数据
	}
  crc= crc16(&modbus.rcbuf[0], modbus.recount-2);                             //计算校验码
  rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1];  //收到的校验码
  if(crc ==  rccrc)                                                           //数据包符合CRC校验规则
	{ 
	  if(modbus.rcbuf[0] == modbus.myadd)         //确认数据包是否是发给本设备的 
		{
		  switch(modbus.rcbuf[1])                 //分析功能码
			{
			case 0:     break;
			case 1:     break;
		    case 2:     break;
		    case 3:     Modbud_fun3();    break;   //3号功能码处理
		    case 4:     break;
		    case 5:     break;
		    case 6:     Modbud_fun6();     break;  //6号功能码处理
	         case 7:     break;						
			}
		}
		else if(modbus.rcbuf[0] == 0)             //广播地址,不处理
		{
		}
	}                                            //数据包不符合CRC校验规则
	modbus.recount=0;                            //清除缓存计数
  modbus.reflag=0;	                             //重新开始执行处理函数C
}

diagramme de flux de processus

modbus.reflag==0
modbus.reflag==1
不符合
符合
不是
广播地址
开始
是否接受完数据,并开始处理
计算校验码
数据包是否符合CRC校验规则
确认数据发给本设备地址
数据包是否发给本设备地址
分析功能码功能
按功能处理数据
结束处理并将计数和使能关掉

1,6, le programme du code de fonction

Code Fonction programme 6

void Modbud_fun6()                             //6号功能码处理,写寄存器
{
  unsigned int Regadd;
	unsigned int val;
	unsigned int i,crc,j;
	i=0;
  Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];    //得到要修改的地址 
	val=modbus.rcbuf[4]*256+modbus.rcbuf[5];     //修改后的值
	Reg[Regadd]=val;                             //修改本设备相应的寄存器
	//以下为回应主机
	modbus.Sendbuf[i++]=modbus.myadd;            //发送本设备地址
    modbus.Sendbuf[i++]=0x06;                    //发送功能码 
    modbus.Sendbuf[i++]=Regadd/256;              //发送修改地址高位
	modbus.Sendbuf[i++]=Regadd%256;              //发送修改地址低位
	modbus.Sendbuf[i++]=val/256;                 //发送修改的值高位
	modbus.Sendbuf[i++]=val%256;                 //发送修改的值低位
	crc=crc16(modbus.Sendbuf,i);                 //校验地址、功能码、地址、数据
	modbus.Sendbuf[i++]=crc/256;                 //发送CRC的值高位
	modbus.Sendbuf[i++]=crc%256;                 //发送CRC的值低位	
	for(j=0;j<i;j++)                             //通过串口逐个发送
   Usart_SendByte( DEBUG_USART,modbus.Sendbuf[j]);
}

programme Code de fonction No. 3

void Modbud_fun3(void)                           //3号功能码处理  ---主机要读取本从机的寄存器
{
  u16 Regadd;
	u16 Reglen;
	u16 byte;
	u16 i,j;
	u16 crc;
	Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //得到要读取的寄存器的首地址
	Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5];  //得到要读取的寄存器的数量
	i=0;
	modbus.Sendbuf[i++]=modbus.myadd;           //发送本设备地址
    modbus.Sendbuf[i++]=0x03;                   //发送功能码      
    byte=Reglen*2;                              //要返回的数据字节数
    //modbus.Sendbuf[i++]=byte/256;  
	modbus.Sendbuf[i++]=byte%256;               //发送要返回的数据字节数         
	for(j=0;j<Reglen;j++)
	{
	  modbus.Sendbuf[i++]=Reg[Regadd+j]/256;    //发送读取数据字节数的高位 
	  modbus.Sendbuf[i++]=Reg[Regadd+j]%256;	//发送读取数据字节数的低位 
	}
	crc=crc16(modbus.Sendbuf,i);                //CRC校验
	modbus.Sendbuf[i++]=crc/256;                //发送CRC的值高位
	modbus.Sendbuf[i++]=crc%256;                //发送CRC的值低位	
	for(j=0;j<i;j++)                            //通过串口逐个发送
   Usart_SendByte( DEBUG_USART,modbus.Sendbuf[j]);
}

En troisième lieu, le phénomène de test

Publié quatre articles originaux · louange gagné 5 · Vues 391

Je suppose que tu aimes

Origine blog.csdn.net/xiaoxiaodawei/article/details/105314738
conseillé
Classement