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

TMS320F28388使用sysconfig配置SCI通信(RS485+FIFO+Modbus)

第1章 使用Sysconfig配置TMS320F283xx的SCI通信底层

1.1 为什么选择使用Sysconfig配置代码

我是自学的DSP芯片,用过TMS320F28335,TMS320F28377,之前学习时使用的都是寄存器开发,感觉写代码时对底层的配置非常清晰,但是效率太低,代码底层也比较臃肿,当时就想有没有像STM32那样有标准库最好是HAL库开发,后来才知道是有的,就是sysconfig这个图形化编程工具来配置底层代码,类似STM32中的HAL库开发,更牛的地方是sysconfig芯片配置出的代码是标准库,但是在使用过程中多少会发生一些BUG,现在TI正在慢慢完善这个工具,相信后面会更加好用。本例程使用的是TMS320F28388D来配置SCI。

1.2 使用sysconfig配置SCI

我使用的是CPU1控制用来做SCI通信,有感兴趣的小伙伴试试能不能使用CPU2来控制,手册中说要先使用CPU1来分配资源给CPU2,目前我还没有尝试,欢迎有能够实现的小伙伴分享出方法,废话不多说,直接上操作步骤以及代码解释。
配置SCI通信代码:
在这里插入图片描述

注意:一定要检查时钟的配置是否合适,建议使用上图的配置,时钟太快会导致数据传输不正确,时钟配置可在图中左上方第二个标的时钟树中改变。

我使用的是RS485,之前看到一篇文章说RS485的波特率最好不要超过115200,否则数据会发生错乱,这里波特率就选测了115200,可以看出是有误差的,但是我打算使用Modbus RTU协议,能够识别发送数据是否正确,这点误差还是可以接收的,我配置的接收和发送FIFO只有一个数据,是因为Modbus协议数据长度不确定,所以打算一次只接受一位数据,不至于卡在FIFO等待接收数据,若每次发送和接收的数据长度一定,建议还是使用FIFO。

配置完成后编译会报错,不然就是中断配置有问题。这是因为还没有写中断函数,但是sysconfig配置生成的board.h文件中声明了这个中断函数,下一章解决。

第2章 中断配置

2.1 中断向量表的配置以及PIE分组配置

在sysconfig配置完SCI后,就不用管PIE分组以及向量映射了,sysconfig已经帮我们配置好了,所以我们不用去管,只需要在主函数初始化的时候调用 Board_init();//初始化来自SysConfig的配置

main.c文件配置代码:

#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "Interrupt.h"
void main(void)
{Device_init();//初始化所有时钟以及外设Interrupt_initModule();//初始化PIE,清除所有PIE寄存器,失能CPU中断Interrupt_initVectorTable();//初始化PIE向量表Board_init();//初始化来自SysConfig的配置Device_bootCPU2(BOOT_MODE_CPU2);//启动CPU2核心部分EINT;//使能全部中断ERTM;//使能允许调试while(1){GPIO_togglePin(LED1);DEVICE_DELAY_US(1000000);}
}

2.2 中断函数

中断函数代码为:

uint16_t
__interrupt void INT_RS485_RX_ISR(void)
{SCI_readCharArray(RS485_BASE, &receivedData, 1);
//    Modbus_zubao(receivedData);SCI_clearOverflowStatus(RS485_BASE);SCI_clearInterruptStatus(RS485_BASE, SCI_INT_RXFF);Interrupt_clearACKGroup(INT_RS485_RX_INTERRUPT_ACK_GROUP);
}

其中 SCI_readCharArray(RS485_BASE, &receivedData, 1);函数为接收数据函数,接收中断触发后通过该函数将数据读到receivedData中,由其他函数进行调用。

SCI_clearOverflowStatus(RS485_BASE);
SCI_clearInterruptStatus(RS485_BASE, SCI_INT_RXFF);
Interrupt_clearACKGroup(INT_RS485_RX_INTERRUPT_ACK_GROUP);

这三行代码解释为:

(1)清除覆盖标志位

(2)清除SCI的中断标志位

(3)清除中断分组标志位,PIE分组

第3章 printf函数的使用

在SCI使用过程中如果调用发送数据指令,代码为:

void Modbus_SendData(unsigned char *buff,unsigned char len)
{unsigned char t;TX_EN;for(t=0;t<len;t++){SCI_writeCharArray(RS485_BASE, (uint16_t*)&buff[t], 1);DEVICE_DELAY_US(2000);//要与时钟对应}RX_EN;
}

感觉比较臃肿,而且发送数据种类有限,不方便调试使用,我比较喜欢使用printf函数发送数据,使用printf函数需要对SCI进行重映射。

3.1 使用printf但不进行重映射

代码为:

#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "stdio.h"
#include "Interrupt.h"
//int fputc(int ch, FILE *f)
//{
//    if (ch == '\n') {
//        SCI_writeCharBlockingFIFO(RS485_BASE, '\r');
//    }
//    SCI_writeCharBlockingFIFO(RS485_BASE, (uint16_t)ch);
//    return ch;
//}void main(void)
{Device_init();//初始化所有时钟以及外设Interrupt_initModule();//初始化PIE,清除所有PIE寄存器,失能CPU中断Interrupt_initVectorTable();//初始化PIE向量表Board_init();//初始化来自SysConfig的配置EINT;//使能全部中断ERTM;//使能允许调试while(1){printf("hello!\r\n");GPIO_togglePin(LED1);DEVICE_DELAY_US(1000000);}
}

现象为:

(1)监视框现象

在这里插入图片描述

(2)上位机现象

在这里插入图片描述

当在线运行时监视框会打印printf函数里面的内容但上位机接收不到信息。

3.2 使用printf并进行重映射

这部分操作步骤有些多,首先需要增加堆栈大小:

将堆栈改大一些:
在这里插入图片描述

适当就好,不用太大,这里设置为400,这时编译应该会报错说:
#10099-D program will not fit into available memory, or the section contains a call site that requires a trampoline that can’t be generated for this section. run placement with alignment/blocking fails for section “.stack” size 0x400 page 0.

这是因为.cmd文件中的堆栈没有400这么大。将.stack改大一些(可以改变RAMM1大小来改变或者像我一样一样或上其他内存单元)

具体操作方法为:
在这里插入图片描述

主函数代码为:

#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "stdio.h"
#include "Interrupt.h"
int fputc(int ch, FILE *f)
{if (ch == '\n') {SCI_writeCharBlockingFIFO(RS485_BASE, '\r');}SCI_writeCharBlockingFIFO(RS485_BASE, (uint16_t)ch);return ch;
}void main(void)
{Device_init();//初始化所有时钟以及外设Interrupt_initModule();//初始化PIE,清除所有PIE寄存器,失能CPU中断Interrupt_initVectorTable();//初始化PIE向量表Board_init();//初始化来自SysConfig的配置EINT;//使能全部中断ERTM;//使能允许调试while(1){printf("hello!\r\n");GPIO_togglePin(LED1);DEVICE_DELAY_US(1000000);}
}

现象为:

(1)监视框现象

在这里插入图片描述

这是代码未运行前的编译信息,监视框已经不打印信息。

(2)上位机现象
在这里插入图片描述

由上图可知,信息打印到了上位机,这便可以进行调试通信了,但现在不能打印浮点数据。

接下来按照下面操作:

在这里插入图片描述

改重映射代码为:

int fputc(int ch, FILE *f)
{SCI_writeCharBlockingFIFO(RS485_BASE, (uint16_t)ch);return ch;
}
int fputs(const char *_ptr,FILE *_fp)
{uint16_t len;len = strlen(_ptr);SCI_writeCharArray(RS485_BASE,(uint16_t *)_ptr,len);return len;
}

这样便可以打印字符串,整数,浮点数了;

主函数代码为:

#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "stdio.h"
#include "Interrupt.h"
#include "string.h"
int fputc(int ch, FILE *f)
{SCI_writeCharBlockingFIFO(RS485_BASE, (uint16_t)ch);return ch;
}
int fputs(const char *_ptr,FILE *_fp)
{uint16_t len;len = strlen(_ptr);SCI_writeCharArray(RS485_BASE,(uint16_t *)_ptr,len);return len;
}
void main(void)
{Device_init();//初始化所有时钟以及外设Interrupt_initModule();//初始化PIE,清除所有PIE寄存器,失能CPU中断Interrupt_initVectorTable();//初始化PIE向量表Board_init();//初始化来自SysConfig的配置Device_bootCPU2(BOOT_MODE_CPU2);//启动CPU2核心部分EINT;//使能全部中断ERTM;//使能允许调试setvbuf(stdout, NULL, _IONBF, 0);//防止发送堵塞float hcbh=0.0121;int i=2054;while(1){printf("%.4f\r\n",hcbh);printf("hello!\r\n");printf("%d\r\n",i);DEVICE_DELAY_US(1000000);}
}

程序现象为:
在这里插入图片描述

可以看出完成了printf最常见的使用方法,既可以打印字符串,还可以打印整数和浮点数,为调试代码带来了极大的方便。

第4章 判断SCI通信数据接收是否完成

如何判断不定长度的数据接收是否完成?网上有多种方法,比如判断包头包尾,中断空闲方法等等,我比较喜欢使用定时器判断接收超时的方式,即每次接收完数据后开始计时,当在规定时间内没有下个数据接收,那么就确定接收数据已经完成,所以需要配置定时器中断。

配置步骤如下:

在这里插入图片描述

我使用的是TMS320F28388,这款芯片主频为200Mhz,所以进行了200预分频,我配置定时器为1000us进行一次中断来记录时间。

同样配置完成后编译会报错说没有声明定时器中断函数,定时器中断函数的代码为:

__interrupt void INT_Timer_1ms_ISR(void)
{Interrupt_clearACKGroup(INT_Timer_1ms_INTERRUPT_ACK_GROUP);
}

这里我使用的是CPUtimer0,他的中断是受PIE管理的,所以需要每次中断完成后清除PIE中断分组触发标志,如果选择使用CPUtimer1或CPUtimer2,则不需要Interrupt_clearACKGroup(INT_Timer_1ms_INTERRUPT_ACK_GROUP);这句来清除中断标志,因为CPUtimer1和CPUtimer2是CPU直接控制的,没有经过PIE,之前看到别人解释说是为给RTOS操作系统预留的。

这里怎么使用下章会给出代码,先这样配置。

第5章 Modbus RTU协议代码

网上关于Modbus协议的介绍有很多,这里不再赘述,我这里代码只使用了03,06,16功能码,比较简单,代码量不多,我对这个协议接触没多久,有好的方法欢迎伙伴们评论留言,这里直接上代码

5.1 Modbus.c文件代码

/** Modbus.c** Created on: 2024年11月2日* Author: DELL*/
#include "Modbus.h"
#include "driverlib.h"
#include "device.h"
#include "stdio.h"
#include "string.h"
#include "board.h"
unsigned char  Modbus_Addr = 0x01;          //从机地址
unsigned char  Modbus_RX_BUFF[200];    //接收缓冲区2048字节
uint16_t Modbus_RX_CNT=0;        //接收计数器
uint16_t Modbus_RX_EN=0;         //接收倒计时
unsigned char  Modbus_FrameFlag=0;     //帧结束标记
unsigned char  Modbus_TX_BUFF[200];    //发送缓冲区
unsigned char  Modbus_RX_LEN=0;        //接收数据长度unsigned int GetCRC16(unsigned char *ptr,  unsigned char len)
{uint16_t index;unsigned char crcl = 0xFF;  //高CRC字节unsigned char crch = 0xFF;  //低CRC字节unsigned char TabH[] = {  //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} ;unsigned char TabL[] = {  //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} ;while (len--)  //计算指定长度的CRC{index = crcl ^ *ptr++;crcl = crch ^ TabH[index];crch = TabL[index];}return (crcl<<8 | crch);};//Modbus寄存器和单片机寄存器的映射关系uint16_t Modbus_IO[100]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}; //输出开关量寄存器指针(这里使用的是位带操作)//
//发送n个字节数据
//buff:发送区首地址
//len:发送的字节数
void Modbus_SendData(unsigned char *buff,unsigned char len)
{unsigned char t;TX_EN;for(t=0;t<len;t++){SCI_writeCharArray(RS485_BASE, (uint16_t*)&buff[t], 1);DEVICE_DELAY_US(2000);//要与时钟对应}RX_EN;
}
/
//Modbus服务程序,用于处理接收到的数据(请在主函数中循环调用)
uint16_t startRegAddr;
uint16_t RegNum;
uint16_t calCRC;
void Modbus_Service(void)
{uint16_t recCRC;if(Modbus_FrameFlag==1){calCRC = GetCRC16(Modbus_RX_BUFF,Modbus_RX_LEN-2);recCRC=Modbus_RX_BUFF[Modbus_RX_LEN-1]|(((uint16_t)Modbus_RX_BUFF[Modbus_RX_LEN-2])<<8);if(calCRC==recCRC)//CRC校验正确{if(Modbus_RX_BUFF[0]==Modbus_Addr)//地址正确{startRegAddr=(((uint16_t)Modbus_RX_BUFF[2])<<8)|Modbus_RX_BUFF[3];//获取寄存器起始地址switch(Modbus_RX_BUFF[1])//根据不同的功能码进行处理{case 03: //eg: 01 03 0000 000A XXXX ->地址 读多个寄存器功能码{Modbus_03_Solve();break;}case 06: //写单个寄存器{Modbus_06_Solve();break;}case 16: //写多个寄存器{Modbus_16_Solve();break;}}}}Modbus_FrameFlag=0;//复位帧结束标志Modbus_RX_CNT=0;//接收计数器清零Modbus_RX_LEN = 0;}
}
Modbus功能码03处理程序///已验证程序OK
读保持寄存器
void Modbus_03_Solve(void)
{unsigned char i;RegNum= (((uint16_t)Modbus_RX_BUFF[4])<<8)|Modbus_RX_BUFF[5];//获取寄存器数量if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内{Modbus_TX_BUFF[0]=Modbus_RX_BUFF[0];Modbus_TX_BUFF[1]=Modbus_RX_BUFF[1];Modbus_TX_BUFF[2]=RegNum*2;for(i=0;i<RegNum*2;i++){Modbus_TX_BUFF[3+i*2]=(Modbus_IO[startRegAddr+i]/256);//           /先发送高字节--在发送低字节Modbus_TX_BUFF[4+i*2]=(Modbus_IO[startRegAddr+i]%256);//}calCRC=GetCRC16(Modbus_TX_BUFF,RegNum*2+3);Modbus_TX_BUFF[RegNum*2+3]=(calCRC>>8)&0xFF;         //CRC高地位不对吗?  // 先高后低Modbus_TX_BUFF[RegNum*2+4]=(calCRC)&0xFF;Modbus_SendData(Modbus_TX_BUFF,RegNum*2+5);}else//寄存器地址+数量超出范围{Modbus_TX_BUFF[0]=Modbus_RX_BUFF[0];Modbus_TX_BUFF[1]=Modbus_RX_BUFF[1];Modbus_TX_BUFF[2]=0x02; //异常码Modbus_SendData(Modbus_TX_BUFF,3);}}
Modbus功能码06处理程序   //已验证程序OK
写单个保持寄存器
void Modbus_06_Solve(void)
{Modbus_IO[startRegAddr]=Modbus_RX_BUFF[4]<<8;//高字节在前                    修改为高字节在前,低字节在后Modbus_IO[startRegAddr]|=((uint16_t)Modbus_RX_BUFF[5]);//低字节在后Modbus_TX_BUFF[0]=Modbus_RX_BUFF[0];Modbus_TX_BUFF[1]=Modbus_RX_BUFF[1];Modbus_TX_BUFF[2]=Modbus_RX_BUFF[2];Modbus_TX_BUFF[3]=Modbus_RX_BUFF[3];Modbus_TX_BUFF[4]=Modbus_RX_BUFF[4];Modbus_TX_BUFF[5]=Modbus_RX_BUFF[5];calCRC=GetCRC16(Modbus_TX_BUFF,6);Modbus_TX_BUFF[6]=(calCRC>>8)&0xFF;Modbus_TX_BUFF[7]=(calCRC)&0xFF;Modbus_SendData(Modbus_TX_BUFF,8);}
Modbus功能码16处理程序 /已验证程序OK
写多个保持寄存器
void Modbus_16_Solve(void)
{unsigned char i;RegNum= (((uint16_t)Modbus_RX_BUFF[4])<<8)|((Modbus_RX_BUFF[5]));//获取寄存器数量if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内{for(i=0;i<RegNum;i++){Modbus_IO[startRegAddr+i]=Modbus_RX_BUFF[7+i*2]<<8; //低字节在前                 /// 低字节在前,高字节在后正常Modbus_IO[startRegAddr+i]|=((uint16_t)Modbus_RX_BUFF[8+i*2]); //高字节在后}Modbus_TX_BUFF[0]=Modbus_RX_BUFF[0];Modbus_TX_BUFF[1]=Modbus_RX_BUFF[1];Modbus_TX_BUFF[2]=Modbus_RX_BUFF[2];Modbus_TX_BUFF[3]=Modbus_RX_BUFF[3];Modbus_TX_BUFF[4]=Modbus_RX_BUFF[4];Modbus_TX_BUFF[5]=Modbus_RX_BUFF[5];calCRC=GetCRC16(Modbus_TX_BUFF,6);Modbus_TX_BUFF[6]=(calCRC>>8)&0xFF;Modbus_TX_BUFF[7]=(calCRC)&0xFF;Modbus_SendData(Modbus_TX_BUFF,8);}else//寄存器地址+数量超出范围{Modbus_TX_BUFF[0]=Modbus_RX_BUFF[0];Modbus_TX_BUFF[1]=Modbus_RX_BUFF[1]|0x80;Modbus_TX_BUFF[2]=0x02; //异常码Modbus_SendData(Modbus_TX_BUFF,3);}}
void Rec_Buf_clean(unsigned char *point,unsigned char len ) //清空数组
{int p;for (p=0;p<len;p++){point[p]=0;}
}
//用于串口接收中断,进行组包
void Modbus_zubao(unsigned char Res)
{if(Modbus_RX_CNT<200){Modbus_RX_BUFF[Modbus_RX_CNT++]=Res;Modbus_RX_EN = 0;}else{Rec_Buf_clean(Modbus_RX_BUFF,200);Modbus_RX_CNT = 0;}
}
//用于定时器,判断是否接收完成#define Modbus_RX_EN_MAX 18
void Modbus_Is_jieshou(void)
{Modbus_RX_EN++;if((Modbus_RX_EN>Modbus_RX_EN_MAX) &&(Modbus_RX_CNT!=0)){Modbus_RX_LEN = Modbus_RX_CNT;Modbus_FrameFlag=1;//置位帧结束标记Modbus_RX_EN = 0;}
}/*********重映射功能放在了这里********/
int fputc(int ch, FILE *f)
{SCI_writeCharBlockingFIFO(RS485_BASE, (uint16_t)ch);return ch;
}
int fputs(const char *_ptr,FILE *_fp)
{uint16_t len;len = strlen(_ptr);SCI_writeCharArray(RS485_BASE,(uint16_t *)_ptr,len);return len;
}

我把SCI重映射的代码也放在了这里,注意如果已经对重映射定义了,不要再重复定义,避免出错

5.2 Modbus.h文件代码

/** Modbus.h** Created on: 2024年11月2日* Author: DELL*/#ifndef HARDWARE_MODBUS_H_
#define HARDWARE_MODBUS_H_#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "stdio.h"extern uint16_t  Modbus_IO[100];
extern uint16_t  Modbus_RX_CNT;//接收计数器
extern unsigned char Modbus_RX_BUFF[200];//接收缓冲区2048字节
extern unsigned char Modbus_TX_BUFF[200];//接收缓冲区2048字节
extern unsigned char Modbus_FrameFlag;//帧结束标记
extern uint16_t  Modbus_RX_EN;//接收倒计时#define TX_EN GPIO_writePin(Control485,1)//485的TX使能,RX失能
#define RX_EN GPIO_writePin(Control485,0)//485的RX使能,TX失能void Modbus_Service(void);
void Modbus_01_Solve(void);
void Modbus_03_Solve(void);
void Modbus_06_Solve(void);
void Modbus_16_Solve(void);
void Modbus_SendData(unsigned char *buff,unsigned char len);//用于串口接收中断,进行组包
void Modbus_zubao(unsigned char Res);//用于定时器,判断是否接收完成
void Modbus_Is_jieshou(void);
//SCI重映射
int fputc(int ch, FILE *f);
int fputs(const char *_ptr,FILE *_fp);#endif /* HARDWARE_MODBUS_H_ */

5.3 接收主机发来的Modbus协议代码

通过配置完Modbus的文件后,我们需要对接收到的上位机数据进行组包,并判断接收是否完成,这是在SCI接收中断函数和CPUTimer0定时器中完成的,中断代码如下

/** Interrupt.c** Created on: 2025年5月23日* Author: DELL*/
#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "Modbus.h"uint16_t receivedData;
__interrupt void INT_RS485_RX_ISR(void)
{SCI_readCharArray(RS485_BASE, &receivedData, 1);Modbus_zubao(receivedData);GPIO_togglePin(LED3);SCI_clearOverflowStatus(RS485_BASE);SCI_clearInterruptStatus(RS485_BASE, SCI_INT_RXFF);Interrupt_clearACKGroup(INT_RS485_RX_INTERRUPT_ACK_GROUP);
}__interrupt void INT_Timer_1ms_ISR(void)
{Modbus_Is_jieshou();Interrupt_clearACKGroup(INT_Timer_1ms_INTERRUPT_ACK_GROUP);
}

这样Modbus就完成了组包,可以完整的接收到Modbus的一帧数据,在Modbus通信使用时,我们需要对数据实时收发,需要在主函数中循环调用Modbus_Service();此时编译很可能会报错说:
#10099-D program will not fit into available memory, or the section contains a call site that requires a trampoline that can’t be generated for this section. run placement with alignment/blocking fails for section “.data” size 0x193 page 0.

这是因为存放代码数据的.data段内存不够了,我们需要对其分配到其他数据内存,操作如下
在这里插入图片描述

从上图可知,我的很多数据都分配到了RAMLS5中,导致内存溢出,我的RAMLS5没有使用过,我将RAMLS5分配给了.data,编译后程序不再报错。

5.4 Modbus程序测试

写完代码后一定要做测试,Modbus程序测试可以使用Modbus Poll这个Modbus主站工具搭配RS485收发器。下载方法网上有很多,不再赘述,可在网上自行下载。

测试结果为下:

我将Modbus从站的前20个寄存器数据设置为了0~19,可以在Modbus.c文件的Modbus_IO[100]数组进行更改

(1)03功能码(读多个寄存器数据)

测试结果为:

在这里插入图片描述

我发送的指令为01 03 00 00 00 0A C5 CD,报文解释为读取ID为01的从站的前10位数据,从机返回的帧格式带前10位数据,符合Modbus RTU协议的定义。

(2)06功能码(写单个保持寄存器)

测试结果为:

在这里插入图片描述

我首先发送指令01 06 00 01 00 0A 5B 0D,报文解释为把ID号为01的从站的第1个寄存器值写为0A(十进制为10),从机返回收到的值,符合modbus协议规定。当执行完成后我又使用了03功能码读取了前10为数据,发现第1为数据变成了10,说明写单个寄存器功能正确。

16功能码(写多个寄存器)

测试结果如下:

在这里插入图片描述

红色框内为我发送的数据,报文解释为将从站地址为01的0~9这10个寄存器改成了10到1,从机回复报文01 10 00 00 00 0A 40 0E,符合Modbus协议规定。我再通过03功能码读取数据发现读取数据正确,证明16功能码正确,通信正常。

这篇文章想要记录的东西太杂了,很多地方没有解释清除,若有好的解决方法或通信方案还望指出,谢谢!

相关文章:

  • Python训练营打卡 Day37
  • 行为型:责任链模式
  • 认知无线电系统中采用能量检测频谱感知
  • pytorch简单线性回归模型
  • 黑马点评--缓存更新策略及案例实现
  • ubuntu脚本常用命令
  • Halcon 图像预处理②
  • AI时代新词-数字孪生(Digital Twin)
  • 并发的产生及对应的解决方案之服务架构说明
  • 大模型Agent
  • [开源项目] 一款功能强大的超高音质音乐播放器
  • 无网络docker镜像迁移
  • 曲线匹配,让数据点在匹配数据的一侧?
  • ADS学习笔记(五) 谐波平衡仿真
  • 电子电路原理第十七章(线性运算放大器电路的应用)
  • 开疆智能Profinet转Profibus网关连接韦普泰克工业称重仪表配置案例
  • 【Qt开发】输入类控件
  • Python 字符串相似度计算:方法、应用与实践
  • WeakAuras Lua Script [ICC BOSS 11 - Sindragosa]
  • ROS2学习(10)------ROS2参数
  • 企业先做网站还是先做淘宝/九江seo公司
  • 东丽天津网站建设/ciliba磁力搜索引擎
  • 天津做网站首选津坤科技b/竞价广告
  • 视频网站做app还是h5/兰州seo优化公司
  • 保定网站建设冀icp/手机端网站排名
  • 企业网站关于我们/深圳网站seo