A33之485串口通信
1. 485通信参考电路
接收发送控制端口为PH5。 电路电平已反向。
2. bsp_modbus.h
#ifndef __BSP_MODBUS_H__
#define __BSP_MODBUS_H__
#include "../config.h"
#include "bsp_uart.h"
#include<pthread.h>
#ifndef MODBUS_MODULE
#define MODBUS_EXT extern
#else
#define MODBUS_EXT
#endif//MODBUS_MODULE
#define CODE_READ_MUL_REGISTER 3
#define CODE_READ_ONLYREAD_REGISTER 4
#define CODE_WRITE_ONE_REGISTER 6
#define CODE_WRITE_MUL_REGISTER 16
#define CODE_READ_MUL_ERR_REGISTER (0X80+CODE_READ_MUL_REGISTER)
#define CODE_WRITE_MUL_ERR_REGISTER (0X80+CODE_WRITE_MUL_REGISTER)
#defineCODE_READ_ONLYREAD_ERR_REGISTER (0X80+CODE_READ_ONLYREAD_REGISTER)
#define CODE_WRITE_ONE_ERR_REGISTER (0X80+CODE_WRITE_ONE_REGISTER)
//以下为功能码
//03:读一个或多个保持寄存器请求数据包:地址-功能码-起始地址-寄存器个数 校验码 先高后低
// 应答数据包:地址-功能码-字节数-寄存器值 校验码
// 错误数据包:地址-错误码-异常码 校验码
//04:读输入寄存器(只读) 请求数据包:地址-功能码-起始地址-寄存器个数 校验码
// 应答数据包:地址-功能码-字节数-寄存器值 校验码
// 错误数据包:地址-错误码-异常码 校验码
//06:写单个寄存器 请求数据包:地址-功能码-寄存器地址-寄存器值 校验码
// 应答数据包:地址-功能码-寄存器地址-寄存器值 校验码
// 错误数据包:地址-错误码-异常码 校验码
//16:写多个寄存器 请求数据包:地址-功能码-寄存器起始地址-寄存器数量-字节数-寄存器值 校验码
// 应答数据包:地址-功能码-寄存器起始地址-寄存器数量 校验码
//
#define IS_MODBUS_CODE(code) ((code == CODE_READ_MUL_REGISTER)\
|| (code == CODE_READ_ONLYREAD_REGISTER)\
|| (code == CODE_WRITE_ONE_REGISTER) \
|| (code == CODE_WRITE_MUL_REGISTER))
#define IS_MODBUS_CODE_AND_ERR(code) ((code == CODE_READ_MUL_REGISTER)\
|| (code == CODE_READ_ONLYREAD_REGISTER)\
|| (code == CODE_WRITE_ONE_REGISTER) \
|| (code ==CODE_WRITE_MUL_REGISTER) \
||(code == CODE_READ_MUL_ERR_REGISTER)\
||(code == CODE_READ_ONLYREAD_ERR_REGISTER)\
|| (code == CODE_WRITE_ONE_ERR_REGISTER) \
|| (code == CODE_WRITE_MUL_ERR_REGISTER))
typedef struct {
intfd;
intsec;
intusec;
charF_rx_ok;
unsignedint speed;
}Uart_info;
Uart_info modbus_info;
MODBUS_EXT char BackRxBuffer1[100];
MODBUS_EXT unsigned char RxBufferSize1;
MODBUS_EXT pthread_mutex_t modbus_mutx;
3. bsp_modbus.c
#include "bsp_modbus.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/signal.h>
#include<fcntl.h>
#include<termios.h>
#include<errno.h>
A> 校验 CRC16查表
/************************CRC高位和地位字节值表***************************/
const unsigned char chCRCHTalbe[] = // CRC 高位字节值表
{
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
};
const unsigned char chCRCLTalbe[] = // CRC 低位字节值表
{
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
};
/***********************************************************
函 数 名: CRC16
* 功能说明: CRC校验码生成函数
* 本代码中使用查表法,以提高运算速度
* 参 数:void
* 返 回 值: void
* 使用说明:
* 调用方法:CRC16 ();
**********************************************************/
u16 CRC16(u8 *puchMsg, u16 DataLen)
{
u8uchCRCHi = 0xFF ; /* 高CRC字节初始化 */
u8uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */
u16Index ; /* CRC循环中的索引 */
while(DataLen--) /* 传输消息缓冲区 */
{
Index= uchCRCLo ^ *puchMsg++ ; /* 计算CRC */
uchCRCLo= (u8)(uchCRCHi ^ chCRCHTalbe[Index]) ;
uchCRCHi= (u8)(chCRCLTalbe[Index]) ;
}
return((u16)uchCRCHi << 8) | uchCRCLo ;
}
/***********************************************************
* 函 数 名: code_crc_check
* 功能说明: CRC校验
* 参 数:u8* rxbuffer, u8 rxsize
* 返 回 值: u8 1:正确 0:错误
* 使用说明:
* 调用方法:code_crc_check();
**********************************************************/
u8 code_crc_check(u8* rxbuffer, u8rxsize)
{
u16crcdata = 0;
if(rxbuffer== NULL || rxsize < 2) return 0;
crcdata= CRC16(rxbuffer, rxsize-2);
if(crcdata== (((u16)rxbuffer[rxsize-1]<<8)+rxbuffer[rxsize-2])) //如果校验正确
return1;
else
return0;
}
B> 协议相关
void GetRegisterVal(u16 addr, u16* data); //获取寄存器的值
void SetRegisterVal(u16 addr, u16 data); //设置寄存器的值
void code_read_mul_register(u8* rxbuffer,u8 rxsize, u8* txbuffer, u8* txsize); //读取多个寄存器的值
void code_read_onlyread_register(u8*rxbuffer, u8 rxsize, u8* txbuffer, u8* txsize); //读取单个寄存器的值
void code_write_one_register(u8* rxbuffer,u8 rxsize, u8* txbuffer, u8* txsize); //写单个寄存器的值
void code_write_mul_register(u8* rxbuffer,u8 rxsize, u8* txbuffer, u8* txsize); //写多个寄存器
C> 485发送接收模式选择
#define MODE_FILEPATH "/sys/class/gpio_sw/PH5/data"
#define TX_MODE 1
#define RX_MODE 0
/**********************************************************
* 函 数 名:Modbus_TxRx_Mode
* 功能说明: 485模式选择
* 参 数:unsigned char
* 返 回 值: void
* 使用说明:
* 调用方法:Modbus_TxRx_Mode(mode);
***********************************************************/
void Modbus_TxRx_Mode(unsigned charmode)
{
FILE*fp;
if((fp= fopen(MODE_FILEPATH, "rb+")) == NULL)
{
perror("Cannotopen value file!\n");
exit(1);
}
if(mode== RX_MODE)
fprintf(fp,"1");
else
fprintf(fp,"0");
fclose(fp);
}
D>串口相关
int Set_Option(int fd,int nSpeed,intnBits,char nEvent,int nStop)
{
struct termios newtio,oldtio;
if(tcgetattr(fd,&oldtio)!=0) //获取当前配置选项 存储到oldstdio结构体中
{
perror("error:SetupSerial!\n");
return -1;
}
bzero(&newtio,sizeof(newtio));
//使能串口接收
newtio.c_cflag |= CLOCAL | CREAD; //忽略调制解调器线路状态,使用接收器
newtio.c_cflag &= ~CSIZE; //字符长度
newtio.c_lflag &=~ICANON;//原始模式
//newtio.c_lflag |=ICANON; //标准模式
//设置串口数据位
switch(nBits)
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |=CS8;
break;
}
//设置奇偶校验位
switch(nEvent)
{
case 'O':
newtio.c_cflag |= PARENB; //使用奇偶校验
newtio.c_cflag |= PARODD; //对输入使用奇偶校验,对输出使用偶校验
newtio.c_iflag |= (INPCK |ISTRIP);//允许输入奇偶校验,去除字符的第8个比特
break;
case 'E':
newtio.c_cflag |= PARENB; //使用奇偶校验
newtio.c_cflag &= ~PARODD;
newtio.c_iflag |= (INPCK |ISTRIP);
break;
case 'N':
newtio.c_cflag &=~PARENB; //不使用奇偶校验
break;
}
//设置串口波特率
switch(nSpeed)
{
case 2400:
cfsetispeed(&newtio,B2400); //设置输入速度
cfsetospeed(&newtio,B2400); //设置输出速度
break;
case 4800:
cfsetispeed(&newtio,B4800);
cfsetospeed(&newtio,B4800);
break;
case 9600:
cfsetispeed(&newtio,B9600);
cfsetospeed(&newtio,B9600);
break;
case 19200:
cfsetispeed(&newtio,B19200);
cfsetospeed(&newtio,B19200);
break;
case 38400:
cfsetispeed(&newtio,B38400);
cfsetospeed(&newtio,B38400);
break;
case 115200:
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
break;
case 230400:
cfsetispeed(&newtio,B230400);
cfsetospeed(&newtio,B230400);
break;
default:
cfsetispeed(&newtio,B19200);
cfsetospeed(&newtio,B19200);
break;
}
//设置停止位
if(nStop == 1)
newtio.c_cflag &= ~CSTOPB; //设置一个停止位
else if(nStop == 2)
newtio.c_cflag |= CSTOPB; //设置两个停止位
newtio.c_cc[VTIME] = 1;
newtio.c_cc[VMIN] = 100; //读满100个或者计时器超时1*0.1S的时候立即返回
tcflush(fd,TCIFLUSH);
if(tcsetattr(fd,TCSANOW,&newtio)!=0)
{
perror("com set error\n");
return -1;
}
return 0;
}
/**********************************************************
* 函 数 名: Modbus_Send
* 功能说明: 485发送数据
* 参 数:char *Data, unsigned int lenth
* 返 回 值: int
* 使用说明:
* 调用方法:Modbus_Send(Data, lenth);
***********************************************************/
int Modbus_Send(char *Data, unsigned intlenth)
{
size_tnleft;
intnwrite;
constchar *ptr;
intfd = modbus_info.fd;
if(lenth== 0)
lenth= strlen(Data);
Modbus_TxRx_Mode(TX_MODE);
usleep(10);
ptr= Data;
nleft= lenth;
while(nleft > 0)
{
if((nwrite = write(fd, ptr, nleft))<= 0)
{
if(errno == EINTR)
{
nwrite= 0;
DEBUG("nwrite=0!\n\r");
}
else
{
perror("nwrite=-1!\n\r");
return(-1);
}
}
nleft-= nwrite;
ptr+= nwrite;
}
if(modbus_info.speed== 115200)
usleep((lenth/10+1)*1000);
else
usleep((lenth/2+1)*1000);
Modbus_TxRx_Mode(RX_MODE);
}
/**********************************************************
* 函 数 名:Modbus_Pthread_Rx
* 功能说明: 485接收数据
* 参 数:void *arg
* 返 回 值: void*
* 使用说明:
* 调用方法:Modbus_Pthread_Rx(arg);
***********************************************************/
void* Modbus_Pthread_Rx(void *arg)
{
intfd;
intretval;
intlen;
inti;
structtimeval tv;
fd_set rfds;
Uart_info *uinfo = (Uart_info*)arg;
char rx_data[100]={0};
fd =uinfo->fd;
tv.tv_sec = uinfo->sec;
tv.tv_usec = uinfo->usec;
while(1)
{
FD_ZERO(&rfds); //把可读文件描述符的集合清空
FD_SET(fd,&rfds ); //把文件描述符加入到集合中
retval = select(fd+1,&rfds, NULL, NULL, &tv);
if(retval == -1)
{
perror("error\r\n");
break;
}
else if(retval == 0)
{
//DEBUG("no data,waiting...\n");
continue;
}
else
{
if(!FD_ISSET(fd,&rfds))
continue;
memset(rx_data, 0, 100);
len = read(fd, rx_data, 100);
if(retval == 1 && len == 0)
{
if(errno== EINTR)
continue;
else
break; //断开连接
}
if(len== 0)
continue;
if(!Mod_Rev_process(rx_data, len)) //数据格式不正确,跳过不处理
{
DEBUG("datafomart is failed!\r\n");
continue;
}
Modbus_Send (BackRxBuffer1,RxBufferSize1); //
}
}
}
pthread_exit(NULL);
}
void* Modbus_Pthread(void *arg)
{
intfd;
fd= open(Modbus_Dev, O_RDWR | O_NOCTTY);
if(fd== -1)
{
perror("openttys2 failed!\r\n");
pthread_exit(NULL);
}
//modbus_info.speed= 115200;
modbus_info.speed= 19200;
if(Set_Option(fd,modbus_info.speed, 8, 'N', 1) == -1)
{
perror("setttys2 failed!\r\n");
pthread_exit(NULL);
}
DEBUG("init ttys2 over!\r\n");
modbus_info.fd= fd;
modbus_info.sec= 0;
modbus_info.usec= 10000;
pthread_create(&pth_modbus_rx,NULL, Modbus_Pthread_Rx, ( void *)&modbus_info);
pthread_detach(pth_modbus_rx); //线程分离,线程结束后自动释放资源
while(0==pthread_kill(pth_modbus_rx,0) ) //发送空信号0给子线程,看是否存在
{
sleep(1);
}
DEBUG("receive thread isquited\r\n");
pthread_exit(NULL);
}
int Modbus_Thread_Create(void)
{
returnpthread_create(&pth_modbus, NULL, Modbus_Pthread, NULL);
}
int Modbus_Thread_Destroy(void)
{
pthread_join(pth_modbus,NULL);
return0;
}
/********************************************************************
* 函 数 名: Mod_Rev_process
* 功能说明: 485从机接收数据的处理函数
* 参 数:char *recv, int len
* 返 回 值: int 是否获取正确格式数据 0=NO 1=YES
* 使用说明:用于串口及网络接收数据的处理
* 调用方法:Mod_Rev_process();
******************************************************************/
int Mod_Rev_process(char *recv, int len)
{
unsignedchar RxCounter1 = 0;
char* rx_ptr = recv;
if(len < 5)
return0;
while(rx_ptr < recv+len ) //上次接收到的数据已处理才保存接收数据
{
RxCounter1++;
switch(RxCounter1)
{
case 1:
{
if(*rx_ptr == MAIN_485_IP_ADDR || (*rx_ptr>= MIN_485_ADDR && *rx_ptr <= MAX_485_ADDR )) //是中控主机地址或从设备地址
{
BackRxBuffer1[0] = *rx_ptr;
}
else
{
RxCounter1 = 0; //不是发给本机的不处理
}
break;
}
case 2:
{
if(IS_MODBUS_CODE(*rx_ptr) == 0) //无效的功能码,重新开始接收
{
RxCounter1 = 0;
}
else
{
BackRxBuffer1[1] = *rx_ptr; //保存功能码
}
break;
}
case 3:
{
BackRxBuffer1[2] = *rx_ptr;
switch(BackRxBuffer1[1])
{
caseCODE_READ_MUL_REGISTER:
RxBufferSize1= BackRxBuffer1[2]+5;
break;
case CODE_WRITE_MUL_REGISTER:
case CODE_WRITE_ONE_REGISTER:
RxBufferSize1 = 8;
break;
case CODE_READ_MUL_ERR_REGISTER:
case CODE_READ_ONLYREAD_ERR_REGISTER:
case CODE_WRITE_ONE_ERR_REGISTER:
case CODE_WRITE_MUL_ERR_REGISTER:
RxBufferSize1 = 5;
break;
default:
break;
}
}
default:
{
BackRxBuffer1[RxCounter1- 1] = *rx_ptr;
if(RxCounter1 >= RxBufferSize1)
{
RxCounter1= 0;
return1;
}
break;
}
}
rx_ptr++;
}
return0;
}
以上内容本人已经过实测验证无误。