完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
整体结构
利用USAR中断的方式接受从上位机发来的报文,对与下位机地址不符或者无法通过CRC校验的报文回复到上位机,并利用定时器中断的方式实现该段时间内的错误延时。 外设配置 配置USART口(以USART3为例) 利用USART口中断时间的方式触发USART发送和接受报文。利用TIM定时器进行USART错误延时,其中全局变量如下: u8 self_address; //本机地址 u8 tx3_data[]; // USART3回复上位机 u8 us3_error; /USART3数据错误标志 u8 us3_finish; /USART3接受完成标志 u8 us3_tx_choose; //USART3恢复选择,2回复正确,3回复错误 u8 drive_dat_len; /从下位机发送的字节数 u8 us3_head; /USART3接受报文头标志 u8 us3_end; USART3接受报文尾标志 u8 time3_tmp = 0; 时钟标识位用于定时器延时 其中报文头为0x04,报文尾为0x0d 使能USART3 void USART3_Set_Init(void) { USART_InitTypeDef USART_InitStructure; USART_DeInit(USART3); //USART3缺省值 USART_InitStructure.USART_BaudRate = 19200; /配置波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; 8位数据 USART_InitStructure.USART_StopBits = USART_StopBits_1; 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(USART3, &USART_InitStructure); USART_Cmd(USART3, ENABLE); 打开USART3 USART_ITConfig(USART3,USART_IT_RXNE,ENABLE ); ///中断使能 GPIO_ResetBits(GPIOB,GPIO_Pin_1); /485为接受型号(电路板上此引脚为MAX485接受方向引脚) } USART3中断优先级设置 void NVIC_US3_it_Config(void) { NVIC_InitTypeDef NVIC_InitStruture; /中断结构体 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); /优先组为0 NVIC_InitStruture.NVIC_IRQChannel=USART3_IRQn; //中断为USART3中断 NVIC_InitStruture.NVIC_IRQChannelPreemptionPriority=0; /中断主优先级为0 NVIC_InitStruture.NVIC_IRQChannelSubPriority=0; //中断辅助优先级为0 NVIC_InitStruture.NVIC_IRQChannelCmd=ENABLE; //中断使能 NVIC_Init(&NVIC_InitStruture); } 配置定时器 定时器使能 void Timer3_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruture; TIM_DeInit(TIM3); / TIM_TimeBaseStruture.TIM_Period=(60-1); // TIM_TimeBaseStruture.TIM_Prescaler=(36000-1); / TIM_TimeBaseStruture.TIM_ClockDivision=TIM_CKD_DIV1; / TIM_TimeBaseStruture.TIM_CounterMode=TIM_CounterMode_Up; // TIM_TimeBaseStruture.TIM_RepetitionCounter=0; TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruture); //配置TIM3 TIM_ClearFlag(TIM3,TIM_FLAG_Update); ///清除中断溢出标志 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //中断使能 } 定时器中断优先级 void NVIC_Time3_Config(void) { NVIC_InitTypeDef NVIC_InitStruture; / NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruture.NVIC_IRQChannel=TIM3_IRQn; // NVIC_InitStruture.NVIC_IRQChannelPreemptionPriority=0; / NVIC_InitStruture.NVIC_IRQChannelSubPriority=0; // NVIC_InitStruture.NVIC_IRQChannelCmd=ENABLE; // NVIC_Init(&NVIC_InitStruture); } 接收报文 MODBUS-RTU协议主机的报文结构: 从机地址 操作数 所需要的数据长度 CRC校验码 这里利用一个中断服务函数进行报文接受操作 void USART3_IRQHandler() { u8 us3_tmp; u8 mn,mm,mu; u8 check_xor; 接收的数据校验后的值 u8 us3_tph,us3_tpl; 检查地址用 static u8 us3_head=0; /USART3接收到上位机字头标志 static u8 us3_end=0; 接收到字尾标志 u8 len_u3; us3_tmp=USART_ReceiveData(USART3); USART_ClearFlag(USART3, USART_FLAG_RXNE); // 清标志 if(us3_head==1) { 接收报文 view_dat[us3_it_count]=us3_tmp; ///存报文 us3_it_count++; /报文长度加(接收结束时数值比实际报文长度大一) if(us3_it_count==2) //检查地址,防止下位机不停接收数据或误接收其他下位数据 { if(view_dat[0]>0x39)us3_tph=view_dat[0]-0x37; ASCII转换为普通数据 (地址高位) else us3_tph=view_dat[0]-0x30; if(view_dat[1]>0x39)us3_tpl=view_dat[1]-0x37; /地址低位 else us3_tpl=view_dat[1]-0x30; us3_tph=(us3_tph<<4)+us3_tpl; if(us3_tph!=self_address) /校验本机地址,防止下位机不停接收数据或误接收其他下位数据 { us3_head=0; 字头标志清零等待接收字头 us3_end=0; us3_it_count=0; us3_error=1; USART3地址错误 GPIO_SetBits(GPIOB,GPIO_Pin_1); /485关闭接收 TIM_Cmd(TIM3,ENABLE); ///打开定时器计时30毫秒过后再打开接收数据 } } if(us3_tmp==0x0d) /判断是否就收完成 { us3_head=0; us3_end=1; /接收结束标志置一 len_u3=us3_it_count; us3_it_count=0; GPIO_SetBits(GPIOB,GPIO_Pin_1); /485停止接收 转为发送 } } if(us3_head==0) ///接收字头 { if(us3_tmp==0x40)us3_head=1; 已经接收到字头 (0x40是字头) } if(us3_end==1) { us3_head=0; us3_end=0; GPIO_SetBits(GPIOB,GPIO_Pin_1); /485停止接收 转为发送 check_xor=0; mn=len_u3-3; /上位机发来的校验值和报文尾部参与校验所以减3 for(mm=0;mm check_xor^=view_dat[mm]; ///校验 } mn=len_u3-1; ///上位机发来的校验码和报文尾不转换所以减1 for(mm=0;mm if(view_dat[mm]>0x39)view_dat[mm]=view_dat[mm]-0x37; //把ASCII码转换普通数据 else view_dat[mm]=view_dat[mm]-0x30; } mu= (view_dat[len_u3-3]<<4)+view_dat[len_u3-2]; if(check_xor==mu) /合并校验值进行比较 { us3_tmp=((view_dat[4]<<4)&0xf0)+view_dat[5]; /计算要操作的数首地址 drive_da_add=us3_tmp; drive_da_add<<=8; us3_tmp=((view_dat[6]<<4)&0xf0)+view_dat[7]; drive_da_add+=us3_tmp; /计算出要操作的数首地址 drive_dat_len=(view_dat[8]<<4)+view_dat[9]; /要回复上位机的字节数 if(view_dat[3]==0x05) us3_tx_choose=4; else us3_tx_choose=2; //USART3回复上位机选择(2回复正确数据,3回复错误) } else us3_tx_choose=3; //USART3回复上位机选择(2回复正确数据,3回复错误) us3_end=0; us3_finish=1; /接收完成 // if(biter1)biter1=0; // else biter1=1; } GPIO_ResetBits(GPIOB,GPIO_Pin_1); /485停止接收 转为发送 } 当主机发送报文的地址与下位机地址不一致时,下位机一段时间内停止对外接受数据防止冲突,这段时间的延迟利用定时器中断的方式实现。 void TIM3_IRQHandler (void) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); if(time3_tmp==1) { us3_error=0; time3_tmp=0; GPIO_ResetBits(GPIOB,GPIO_Pin_1); /打开USART3的485接收 } if(us3_error==1) //USART3错误延时 { time3_tmp=1; } TIM_Cmd(TIM3,DISABLE); ///关闭定时器3 } CRC校验码生成程序 常用的CRC生成算法主要如下 u16 CRC16_work(u8 *PSrc,u8 Count) //CRC16 计算函数 { u8 i,Temp; u16 CRCC=0xFFFF; for(i=0;i CRCC=CRCC^*(PSrc++); for(Temp=0;Temp<8;Temp++) { if((CRCC&0x01)==1) { CRCC=(CRCC>>1)^0xA001; } else { CRCC=CRCC>>1; } } } return CRCC; } 通常我们可以利用数组的方式配置下位机放松的报文 void us3_work(void) { u8 tx3_tmp1; u8 bz,bx,by,bs; u16 tx_us3_tmp; u8 tx3_check; if(us3_tx_choose==2) //USART3回复选择(2回复正确数据,3回复错误) { tx3_data[0]=0x40; /要发送的字头 tx3_tmp1=self_address; /要发的本机地址 tx3_tmp1=(tx3_tmp1>>4)&0x0f; if(tx3_tmp1>9)tx3_data[1]=tx3_tmp1+0x37; 要发送的地址高位ASCII码 else tx3_data[1]=tx3_tmp1+0x30; tx3_tmp1=self_address; tx3_tmp1&=0x0f; if(tx3_tmp1>9)tx3_data[2]=tx3_tmp1+0x37; 要发送的地址低ASCII码 else tx3_data[2]=tx3_tmp1+0x30; tx3_tmp1=drive_dat_len; //要发的字节数 tx3_tmp1=(tx3_tmp1>>4)&0x0f; if(tx3_tmp1>9)tx3_data[3]=tx3_tmp1+0x37; 要发送字节数高位ASCII码 else tx3_data[3]=tx3_tmp1+0x30; tx3_tmp1=drive_dat_len; tx3_tmp1&=0x0f; if(tx3_tmp1>9)tx3_data[4]=tx3_tmp1+0x37; 要发送字节数低ASCII码 else tx3_data[4]=tx3_tmp1+0x30; by=drive_dat_len; //字节长度 bz=5; /输出数组地址 bs=drive_da_add; //要上位机要的收地址 for(bx=0;bx tx_us3_tmp=read_data[bs]; tx3_tmp1=((tx_us3_tmp>>4)&0x0f); if(tx3_tmp1>9)tx3_data[bz]=tx3_tmp1+0x37; else tx3_data[bz]=tx3_tmp1+0x30; tx3_tmp1=(tx_us3_tmp&0x0f); if(tx3_tmp1>9)tx3_data[bz+1]=tx3_tmp1+0x37; else tx3_data[bz+1]=tx3_tmp1+0x30; bs++; bz=bz+2; } tx3_check=0; for(bx=1;bx tx3_check^=tx3_data[bx]; //进行校验 } tx3_tmp1=(tx3_check>>4); if(tx3_tmp1>9)tx3_data[bz]=tx3_tmp1+0x37; 校验ASCII高位 else tx3_data[bz]=tx3_tmp1+0x30; bz++; tx3_tmp1=(tx3_check&0x0f); if(tx3_tmp1>9)tx3_data[bz]=tx3_tmp1+0x37; /校验ASCII低位 else tx3_data[bz]=tx3_tmp1+0x30; bz++; tx3_data[bz]=0x0d; /报文尾 bz++; for(bx=0;bx tx3_tmp1=tx3_data[bx]; us3_tx(tx3_tmp1); //发送数据 } if(biter)biter=0; //正确恢复上位机数据LED闪烁 else biter=1; } if(us3_tx_choose==3) //回复错误 { tx3_data[0]=0x40; /要发送的字头 tx3_tmp1=self_address; /要发的本机地址 tx3_tmp1=tx3_tmp1>>4; if(tx3_tmp1>9)tx3_data[1]=tx3_tmp1+0x37; 要发送的地址高位ASCII码 else tx3_data[1]=tx3_tmp1+0x30; tx3_tmp1=self_address; tx3_tmp1&=0x0f; if(tx3_tmp1>9)tx3_data[2]=tx3_tmp1+0x37; 要发送的地址低ASCII码 else tx3_data[2]=tx3_tmp1+0x30; tx3_data[3]=0x2a; tx3_data[4]=0x2a; tx3_check=0; for(bx=1;bx<5;bx++) { tx3_check^=tx3_data[bx]; //进行校验 } tx3_tmp1=(tx3_check>>4); if(tx3_tmp1>9)tx3_data[5]=tx3_tmp1+0x37; 校验ASCII高位 else tx3_data[5]=tx3_tmp1+0x30; bz++; tx3_tmp1=(tx3_check&0x0f); if(tx3_tmp1>9)tx3_data[6]=tx3_tmp1+0x37; /校验ASCII低位 else tx3_data[6]=tx3_tmp1+0x30; tx3_data[7]=0x0d; /报文尾 for(bx=0;bx<8;bx++) { tx3_tmp1=tx3_data[bx]; us3_tx(tx3_tmp1); //发送数据 } } if(us3_tx_choose==4) //回复错误 { tx3_data[0]=0x40; /要发送的字头 tx3_tmp1=self_address; /要发的本机地址 tx3_tmp1=tx3_tmp1>>4; if(tx3_tmp1>9)tx3_data[1]=tx3_tmp1+0x37; 要发送的地址高位ASCII码 else tx3_data[1]=tx3_tmp1+0x30; tx3_tmp1=self_address; tx3_tmp1&=0x0f; if(tx3_tmp1>9)tx3_data[2]=tx3_tmp1+0x37; 要发送的地址低ASCII码 else tx3_data[2]=tx3_tmp1+0x30; tx3_data[3]=0x23; tx3_data[4]=0x23; tx3_check=0; for(bx=1;bx<5;bx++) { tx3_check^=tx3_data[bx]; //进行校验 } tx3_tmp1=(tx3_check>>4); if(tx3_tmp1>9)tx3_data[5]=tx3_tmp1+0x37; 校验ASCII高位 else tx3_data[5]=tx3_tmp1+0x30; bz++; tx3_tmp1=(tx3_check&0x0f); if(tx3_tmp1>9)tx3_data[6]=tx3_tmp1+0x37; /校验ASCII低位 else tx3_data[6]=tx3_tmp1+0x30; tx3_data[7]=0x0d; /报文尾 for(bx=0;bx<8;bx++) { tx3_tmp1=tx3_data[bx]; us3_tx(tx3_tmp1); //发送数据 } } GPIO_ResetBits(GPIOB,GPIO_Pin_1); /发送结束485停止发送转为接收 us3_tx_choose=0; //USART3回复选择(2回复正确数据,3回复错误) drive_da_add=0; /上位机要操作的数据首地址 drive_dat_len=0; /要回复上位机的字节数 } 最后发送数据 void us3_tx(u8 us3_tx_dat) { USART_ClearFlag(USART3, USART_FLAG_TC); // Çå±êÖ¾ USART_SendData(USART3, us3_tx_dat); //·¢ËÍÊý¾Ý while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1261 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1268 浏览 1 评论
668 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
498 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1262 浏览 2 评论
1700浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
393浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
363浏览 3评论
350浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
328浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-9-13 10:14 , Processed in 1.175005 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号