关于ok335xSII支持串口Mark/Space校验位的变通实现方法
一)思路
1.对于ok335xSII开发板使用POSIX不支持Mark/Space串口校验问题,我的解决方法是变通:
首先,我们了解一下串口的校验方式:N:无校验位,O:奇校验位,E:偶效验位,M:1校验位,S:0校验位。N无校验位输出,O保证带校验位,数据有奇数个1,E保证带校验位,数据有偶数个1,M校验位始终为1,S校验位始终为0。当有校验位时,应根据需要给定模式,但实际上linux系统中,支持POSIX越好,对于M和S的支持越不好。
其次,解决M,S支持的变通方法处理。既然M是1校验,S是0校验,只要保证数据校验位正确即可,我们使用O,E完成这个过程的处理,首先确保您的O,E校验位可以有效使用,不确定可以使用示波器查看停止位前的第9位有无。串口数据格式:低开始位(1位),数据7/8位(先发低位,后发高位),校验位(1位),停止位(1/2位)。方法如下:校验位要S模式时,统计该字节数据字节的1出现的次数,为偶数次时给定E,为奇数次时给定O;校验位要M模式时,统计该字节数据字节的1出现的次数,为偶数次时给定O,为奇数时给定E。
再次,一般使用多机模式时,会用到M/S校验位切换,地址使用M,数据使用S。为了处理这样的过程,我们需要编写校验位切换函数,以达到多机通讯的目的,在接收数据时,使用N校验接收,不关心校验位。
二)支持代码
serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#ifdef __cplusplus
extern "C" {
#endif
/*头文件*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
/*调试宏*/
//#define DEBUG
#ifdef DEBUG
#define pr_debug(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...)
#endif
//MARK/SPACE校验用宏
#define CMSPAR 010000000000
/*设备文件名*/
#define SERIAL_DEVFILE_NAME0 "/dev/ttyO1" //整机和1-12功放串口文件名,RS485
#define SERIAL_DEVFILE_NAME1 "/dev/ttyO2" //激励器1串口文件名(备用口),TTL
#define SERIAL_DEVFILE_NAME2 "/dev/ttyO3" //激励器2或13-24功放串口文件名,TTL
/*SERIAL数据结构*/
struct serial_cmd {
unsigned int bps; //波特率
unsigned char databits; //数据位
unsigned char stopbits; //停止位
unsigned char parity; //奇偶校验位
unsigned char reserved;
};
/*
函数功能:打开SERIAL设备
参数:SERIAL_DEVFILE_NAME0或SERIAL_DEVFILE_NAME1
返回值:打开设备成功,返回设备文件描述符fd;失败返回-1
*/
extern int serial_open(const char*filename);
/*
函数功能:关闭SERIAL设备
参数:
@fd:设备文件描述符
返回值:无
*/
extern void serial_close(int fd);
/*
函数功能:配置SERIAL的工作参数
参数:
@fd:设备文件描述符;
@serialctrl:控制SERIAL命令
返回值:操作成功,返回0,否则返回-1
*/
extern int serial_config(int fd, struct serial_cmd *serialctrl);
/*
函数功能:特殊的配置SERIAL的工作参数
参数:
@fd:设备文件描述符;
@serialctrl:控制SERIAL命令
返回值:操作成功,返回0,否则返回-1
说明:只更改MARK/SPACE,不刷新缓冲区
*/
extern int read_serial_config(int fd, struct serial_cmd *serialctrl);
/*
函数功能:读取串口
参数:
@fd:设备文件描述符;
返回值:操作成功,返回0,否则返回-1
*/
extern int serial_read(int fd, char *buf, int size);
/*
函数功能:写串口
参数:
@fd:设备文件描述符;
返回值:操作成功,返回0,否则返回-1
*/
extern int serial_write(int fd, char *buf, int size);
#ifdef __cplusplus
}
#endif
#endif
serial.c
#include "serial.h"
int serial_open(const char*filename)
{
int fd = open(filename, O_RDWR|O_NOCTTY|O_SYNC);//不成为控制终端的控制程序(O_NOCTTY),并在完全写入硬件后返回(O_SYNC)
if (fd < 0) {
pr_debug("open serial device failed.\n");
return -1;
}
pr_debug("open serial device successfully!\n");
return fd;
}
void serial_close(int fd)
{
pr_debug("close serial device...\n");
close(fd);
}
static int speed_arr[] = {B115200,B38400,B19200,B9600,B4800,B2400,B1200,B300};
static int bps_arr[] = {115200,38400,19200,9600,4800,2400,1200,300};
int serial_config(int fd, struct serial_cmd *serialctrl)
{
int i;
int ret;
int status;
struct termios opt; //串口设置结构体
//检错
if (fd < 0) {
pr_debug("fd is invalid.\n");
return -1;
}
if (serialctrl == NULL) {
pr_debug("serialctrl is invalid.\n");
return -1;
}
/*配置波特率*/
if (tcgetattr(fd,&opt) != 0) //获取串口默认属性
{
pr_debug("Get Serial Options Error");
return(-1);
}
for(i=0;i<sizeof(speed_arr)/sizeof(int);i++) {
if(serialctrl->bps==bps_arr[i]) //获取原有信息
{
tcflush(fd,TCIOFLUSH); //刷新IO缓冲区
cfsetispeed(&opt,speed_arr[i]); //配置输入波特率
cfsetospeed(&opt,speed_arr[i]); //配置输出波特率
status = tcsetattr(fd,TCSANOW,&opt); //设置到硬件寄存器上,TCSANOW立即执行不等接受完,TCSADRAIN等待所有数据传递完成之后执行,TCSAFLUSH刷新输入输出缓冲,并且做出改变
if(status != 0)
{
pr_debug("Set Serial Bps Error");
return -1;
}
}
tcflush(fd,TCIOFLUSH); //刷新IO缓冲区
}
pr_debug("config serial %d bps successfully!\n", serialctrl->bps);
/*配置数据位,停止位,奇偶校验位*/
if(tcgetattr(fd,&opt) != 0)
{
pr_debug("Get Serial Options Error");
return(-1);
}
opt.c_cflag &= ~CSIZE;//控制模式
opt.c_iflag &= ~(IXON|IXOFF|IXANY); //用于解决0x11,0x13不能接受的问题,软件流控制屏蔽
opt.c_iflag &= ~(INLCR|ICRNL|IGNCR);//输入屏蔽回车,换行等字符控制,0x0a,0x0d
opt.c_oflag &= ~(ONLCR|OCRNL); //输出屏蔽回车,换行等字符控制
//set data bit length
switch (serialctrl->databits) /*设置数据位数*/
{
case 7:
opt.c_cflag |= CS7;
break;
case 8:
opt.c_cflag |= CS8;
break;
default:
pr_debug("Unsupported data size!\n");
return (-1);
}
pr_debug("config serial %d databits successfully!\n", serialctrl->databits);
//set parity bit mode
switch (serialctrl->parity) //设置校验方式
{
case 'n':
case 'N':
opt.c_cflag &= ~PARENB; /* Clear parity enable */
opt.c_iflag &= ~INPCK; /* Disable parity checking */
break;
case 'o':
case 'O':
opt.c_cflag |= (PARODD|PARENB); /* 设置为奇效验*/
opt.c_iflag |= INPCK; /* Enable parity checking */
break;
case 'e':
case 'E':
opt.c_cflag |= PARENB; /* Enable parity */
opt.c_cflag &= ~PARODD; /* 转换为偶效验*/
opt.c_iflag |= INPCK; /* Enable parity checking */
break;
case 's':
case 'S': /*as no parity*/
opt.c_cflag |= PARENB|CMSPAR;
opt.c_cflag &=~PARODD;
opt.c_iflag |= INPCK; /*Enable parity checking*/
break;
case 'm': /*mark parity*/
case 'M':
opt.c_cflag |= PARENB|CMSPAR|PARODD;
opt.c_iflag |= INPCK; /*Enable parity checking*/
break;
default:
pr_debug("Unsupported parity\n");
return (-1);
}
pr_debug("config serial %c databits successfully!\n", serialctrl->parity);
//set stop bit length
switch (serialctrl->stopbits) //设置停止位
{
case 1:
opt.c_cflag &= ~CSTOPB;
break;
case 2:
opt.c_cflag |= CSTOPB;
break;
default:
pr_debug("Unsupported stop bits\n");
return (-1);
}
pr_debug("config serial %d stopbits successfully!\n", serialctrl->stopbits);
opt.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);//本地控制标志,行方式输入,不经处理直接发送
opt.c_oflag &= ~OPOST;
opt.c_cflag |= (CLOCAL | CREAD);
//读取超时200ms,最小字节0个,全为0时,不阻塞
opt.c_cc[VTIME] = 2;
opt.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH);//刷新输入缓冲区 //Update the options and do it NOW
if (tcsetattr(fd,TCSANOW,&opt) != 0) //设置属性
{
pr_debug("Set Serial Options Error");
return (-1);
}
pr_debug("config serial device successfully!\n");
return 0; //正常返回
}
int read_serial_config(int fd, struct serial_cmd *serialctrl)
{
struct termios opt; //串口设置结构体
//检错
if (fd < 0) {
pr_debug("fd is invalid.\n");
return -1;
}
if (serialctrl == NULL) {
pr_debug("serialctrl is invalid.\n");
return -1;
}
/*配置校验位*/
if(tcgetattr(fd,&opt) != 0) //获取属性
{
pr_debug("Get Serial Options Error");
return(-1);
}
//set parity bit mode,设置校验方式
switch (serialctrl->parity)
{
case 'n': //无校验
case 'N':
opt.c_cflag &= ~PARENB; /* Clear parity enable */
opt.c_iflag &= ~INPCK; /* Disable parity checking */
break;
case 'o': //奇校验
case 'O':
opt.c_cflag |= (PARODD|PARENB); /* 设置为奇效验*/
opt.c_iflag |= INPCK; /* Enable parity checking */
break;
case 'e': //偶校验
case 'E':
opt.c_cflag |= PARENB; /* Enable parity */
opt.c_cflag &= ~PARODD; /* 转换为偶效验*/
opt.c_iflag |= INPCK; /* Enable parity checking */
break;
case 's': //零校验
case 'S': /*as no parity*/
opt.c_cflag |= PARENB|CMSPAR;
opt.c_cflag &=~PARODD;
opt.c_iflag |= INPCK; /*Enable parity checking*/
break;
case 'm': /*mark parity,一校验*/
case 'M':
opt.c_cflag |= PARENB|CMSPAR|PARODD;
opt.c_iflag |= INPCK; /*Enable parity checking*/
break;
default:
pr_debug("Unsupported parity\n");
return (-1);
}
if (tcsetattr(fd,TCSANOW,&opt) != 0) //设置属性
{
pr_debug("Set Serial Options Error");
return (-1);
}
pr_debug("read_serial_config:config serial device successfully!\n");
return 0; //正常返回
}
int serial_read(int fd, char *buf, int size)
{
int ret;
unsigned long data;
if (fd < 0) {
pr_debug("fd is invalid.\n");
return -1;
}
if (buf == NULL) {
pr_debug("buf is invalid.\n");
return -1;
}
if (size < 0) {
pr_debug("size is invalid.\n");
return -1;
}
ret = read(fd, buf, size);
if (ret == -1) {
pr_debug("read serial device failed.\n");
return -1;
}
return ret;
}
int serial_write(int fd, char *buf, int size)
{
int ret;
unsigned long data;
if (fd < 0) {
pr_debug("fd is invalid.\n");
return -1;
}
if (buf == NULL) {
pr_debug("buf is invalid.\n");
return -1;
}
if (size < 0) {
pr_debug("size is invalid.\n");
return -1;
}
ret = write(fd, buf, size);
if (ret == -1) {
pr_debug("write serial device failed.\n");
return -1;
}
return ret;
}
serial_app.h
#ifndef SERIAL_APP_H
#define SERIAL_APP_H
#ifdef __cplusplus
extern "C"{
#endif
// 头文件
#include"serial.h"//串口文件
//调试宏
//#define DEBUG
#ifdef DEBUG
#define pr_debug(fmt, ...) printf(fmt,##__VA_ARGS__)
#else
#define pr_debug(fmt, ...)
#endif
/*
功能:MARK,SPACE转常规校验字
参数:数据字节,需求校验字
返回值:校验字
*/
extern char getMarkSpaceParity(char data,char parity);
/*
功能:发送markspace数据
参数:文件描述符,数组指针,数据大小,串口参数指针,校验字(m,s)
返回值:校验字
*/
extern int serial_write_MarkSpace(int fd, char *buf, int size,struct serial_cmd *serialctrl,char parity);
#ifdef __cplusplus
}
#endif
#endif
serial_app.c
#include"serial_app.h"
/*
功能:MARK,SPACE转常规校验字
参数:数据字节,需求校验字
返回值:校验字
*/
char getMarkSpaceParity(char data,char parity){
unsigned char num=0,i=0;
char ret='n';//返回默认None
for(i=0;i<8;i++){//遍历数据位,统计1个数
char d=data>>i;
if(d&0x01){
num+=1;
}
}
//根据数据
if((parity=='s'||parity=='S')&&(num%2!=0)){//要s(0),数据为奇数
ret='o';//奇校验
}else if((parity=='s'||parity=='S')&&(num%2==0)){//要s(0),数据为偶数
ret='e';//偶校验
}if((parity=='m'||parity=='M')&&(num%2!=0)){//要m(1),数据为奇数
ret='e';//偶校验
}else if((parity=='m'||parity=='M')&&(num%2==0)){//要m(1),数据为偶数
ret='o';//奇校验
}else if(parity=='n'||parity=='N'){//无校验
ret='n';
}else if(parity=='o'||parity=='O'){//奇校验
ret='o';
}else if(parity=='e'||parity=='E'){//偶校验
ret='e';
}
return ret;//返回校验字
}
/*
功能:发送markspace数据
参数:文件描述符,数组指针,数据大小,串口参数指针,校验字(m,s)
返回值:0正常,1错误
*/
int serial_write_MarkSpace(int fd, char *buf, int size,struct serial_cmd *serialctrl,char parity){
int ret=0,i=0;
unsigned long data;
struct serial_cmd newserialctrl;
if (fd < 0) {
pr_debug("fd is invalid.\n");
return -1;
}
if (buf == NULL) {
pr_debug("buf is invalid.\n");
return -1;
}
if (size <=0) {
pr_debug("size is invalid.\n");
return -1;
}
if (parity!='s'&&parity!='S'&&parity!='m'&&parity!='M'&&parity!='n'&&parity!='N'&&parity!='o'&&parity!='O'&&parity!='e'&&parity!='E'){
pr_debug("parity is invalid.\n,your input parity:%c",parity);
return -1;
}
memcpy((void*)&newserialctrl,(void*)serialctrl,sizeof(newserialctrl));//拷贝参数
for(i=0;i<size;i++){//循环发送
newserialctrl.parity=getMarkSpaceParity(buf[i],parity);//获取常规校验
if(read_serial_config(fd, &newserialctrl) == -1){//配置串口参数
printf("serial_config_test:fault\n");
return -1;
}
usleep(50);//延时50us
ret = write(fd, buf+i, 1);//每次发发送一个
if (ret == -1) {
pr_debug("write serial device failed.\n");
return -1;
}
usleep(420);//延时420us,根据实际调整,delay_T(us)=1秒/(波特率/8)*1000000
}
return ret;
}
调用部分
#include "serial_app.h"
int main(int argc, char *argv[])
{
int fd;
struct serial_cmd serialctrl;
char wbuf[100] = {0x40,0x46,0x06,0x01,0x8D,0x96,0x00};
char rbuf[100] ={0};
int i=0;
fd = serial_open(SERIAL_DEVFILE_NAME0);
if (fd < 0)return -1;
serialctrl.bps = 19200;
serialctrl.stopbits = 1;
serialctrl.databits = 8;
serialctrl.parity = 'n';
if (serial_config(fd, &serialctrl) == -1){
printf("serial_config:fault\n");
return -1;
}
while (1) {
usleep(80000);//延时80ms
if(serial_write_MarkSpace(fd, wbuf, 1,&serialctrl,'m') == -1){//发送地址字节,M校验位
printf("serial_write_MarkSpace:fault\n");
return -1;
}
if(serial_write_MarkSpace(fd, wbuf+1, 5,&serialctrl,'s') == -1){//发送数据数据,S校验位
printf("serial_write_MarkSpace:fault\n");
return -1;
}
//切换校验为N
serialctrl.parity = 'n';
if (read_serial_config(fd, &serialctrl) == -1){
printf("read_serial_config:fault\n");
return -1;
}
//循环接收数据,每次接收一个字节
for(i=0;i<100;i++){
if (serial_read(fd, rbuf+i,1) <=0){
printf("serial_read:fault\n");
break;
}
}
printf("recv data is:");
for(i=0;i<100;i++){
printf("0X%X ",rbuf[i]);
}
printf("\n");
}
serial_close(fd);//关闭串口
return 0;
}
三)总结
多机模式的通讯应用在以前的控制系统中比较常见,随着硬件的更新,多机模式的通讯应用不再主流,POSIX的可移植接口标准,对部分需求不再支持,需要我们变通解决问题。
我是Simon,再这里期待您的关注。