找回密码
 立即注册
查看: 91|回复: 9

STC8H8K64U 重新开贴承接上一个帖子

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 6 天前 | 显示全部楼层 |阅读模式

第三十四集 PWM的捕获应用二

接上一章节

3、捕获模式的应用举例
(1)输入捕获模式测量脉冲波形的周期。
原理:使用高级PWM内部的某一通道的捕获模块CCx,捕获外部的端口的
上升沿或者下降沿,两个上升沿之间或者两个下降沿之间的时间即为脉冲的周期
,也就是说,两次捕获计数值的差值即为周期值。
只有PWM1P、PWM2P、PWM3P、PWM4P、PWM5、PWM6、PWM7、
PWM8才有捕获功能。

3、捕获模式的应用举例
例:使用PWMA的第一组捕获模块CC1捕获功能,捕获PWM1P(P6.0)管脚上的上升沿,
在中断中对前后两次的捕获值相减得到周期。
使用P4.2输出周期10ms,占空比2:3的波形进行测试。
截图202506190802458417.jpg 截图202506190803106377.jpg
上面的接线接用的液晶的接口 用的IO口是P4.0P6.0
截图202506190803497594.jpg 截图202506190804289367.jpg 截图202506190804513355.jpg 截图202506190805026273.jpg
截图202506190805233704.jpg 截图202506190805385365.jpg 截图202506190805563150.jpg 截图202506190806092099.jpg 截图202506190806224480.jpg
截图202506190806387390.jpg 截图202506190808213034.jpg 判断第二位是否为1


截图202506190829177086.jpg
第2个例子输入捕获测量高电平的时间。


截图202506201433064350.jpg 周期是50ms
截图202506201434375569.jpg 截图202506201445344802.jpg 截图202506201445575759.jpg 截图202506201446308781.jpg
截图202506201446527160.jpg 下面的是CCR2 截图202506201502349832.jpg 截图202506201504361866.jpg 截图202506201505291244.jpg
截图202506201506177439.jpg 截图202506201516214138.jpg 截图202506201518253186.jpg 截图202506201519289223.jpg 截图202506201520027442.jpg
捕获模式的应用举例
输入捕获模式测量脉冲波形的周期和高电平宽度
原理:使用高级PWM内部的两通道的捕获模块CCx和CCx+1同时捕获外部
的同一个管脚,CCx捕获此管脚的上升沿,CCx+1捕获此管脚的下降沿,同时使
能此管脚的上升沿信号为复位触发信号,CCx的捕获值即为周期,CCx+1的捕获
值即为占空比。
注意:只有CC1+CC2、CC3+CC4、CC5+CC6、CC7+CC8这4种组合才能完成上面的功能
CC1+CC2组合可以同时捕获PWM1P引脚,也可以同时捕获PWM2P引脚:CC3+CC4组合可
以同时捕获PWM3P引脚,也可以同时捕获PWM4P引脚:CC5+CC6组合可以同时捕获PWM5号
脚,也可以同时捕获PWM6引脚:CC7+CC8组合可以同时捕获PWM7引脚,也可以同时捕获
PWM8引脚。

截图202506201534012082.jpg 截图202506201534578795.jpg 截图202506201535113902.jpg
代码
截图202506201535572364.jpg 截图202506201536531734.jpg







截图202506190804055899.jpg
截图202506201449295193.jpg
截图202506201504157995.jpg
回复

使用道具 举报 送花

3

主题

591

回帖

-107

积分

等待验证会员

积分
-107
发表于 6 天前 | 显示全部楼层
第三十四集 PWM的捕获应用二  
在第三十四集中,我们进一步探讨了PWM捕获模式的应用,这一技术在信号处理和系统监控中具有重要意义。通过捕获模式,我们能够精确测量PWM信号的脉冲周期,从而优化系统性能和提高信号稳定性。

一、PWM捕获模式的原理  
捕获模式的核心在于通过测量外部端口的脉冲波形,来获取PWM信号的周期。具体来说,捕获模式可以利用PWM内部的捕获模块(如CCx)来捕获外部端口的上升沿或下降沿。两个相邻的上升沿或下降沿之间的间隔即为脉冲的周期,这一原理在PWM信号处理中具有重要应用。

二、捕获模式的应用举例  
捕获模式的应用可以分为多个方面:

1. 硬件实现  
捕获模式在硬件实现中较为简单,只需要在PWM模块中接入捕获模块即可。通过级联或并联的方式,捕获模块可以捕获外部端口的上升沿或下降沿,从而实现周期的测量。这种方法在硬件设计中占据重要地位。

2. 在汽车系统中的应用  
在汽车系统中,捕获模式被广泛使用以优化信号处理。例如,捕获模式可以用于检测汽车发动机的转速信号,从而实现转速控制系统的实时监测和调节。这种应用不仅提高了系统的响应速度,还增强了系统的稳定性。

3. 在工业系统中的应用  
在工业系统中,捕获模式也被用于信号检测和控制。例如,捕获模式可以用于检测电机的转速信号,从而实现电机控制系统的优化。这种应用不仅提高了工业系统的效率,还减少了能源消耗。

4. 在电子测量中的应用  
捕获模式还被广泛应用于电子测量领域。例如,捕获模式可以用于测量电机转矩或电感的磁通量,从而实现精确的信号处理和控制。这种应用在电子测量系统中占据重要地位。

三、捕获模式的应用案例  
为了更好地理解捕获模式的应用,以下是一些典型的案例:

1. 捕获模式用于信号检测  
在汽车电子系统中,捕获模式被用于检测发动机转速信号。通过捕获模式,可以实现转速信号的实时监测和调节,从而提高车辆的性能和稳定性。

2. 捕获模式用于控制系统的优化  
在工业系统中,捕获模式被用于优化控制系统的响应速度和稳定性。例如,捕获模式可以用于检测并调节控制信号的响应时间,从而实现更精确的控制。

3. 捕获模式用于信号处理  
捕获模式也被用于信号处理领域。例如,捕获模式可以用于检测和分析PWM信号中的脉冲周期,从而实现信号的精确控制和处理。

四、总结  
PWM捕获模式作为信号处理中的重要技术,具有广泛的应用场景。通过捕获模式,我们可以精确测量PWM信号的脉冲周期,从而优化系统的性能和稳定性。这一技术在汽车、工业和电子测量等领域中占据重要地位。

[本文内容由国芯人工智能辅助生成,仅供参考]
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:27
  • 最近打卡:2025-06-19 08:46:31

24

主题

404

回帖

2681

积分

版主

积分
2681
QQ
发表于 6 天前 | 显示全部楼层
在之前的帖子直接以评论方式继续打卡,无需单独开贴
QQ:1463035472,微信:19952583954
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 5 天前 | 显示全部楼层
梁工补充:ADC应用NTC测温
截图202506201949537673.jpg
R1 为参考电阻,要求精度 1%,RT 为负温度系数热敏电阻,标称值为25 度C
时的电阻值,厂家会提供温度阻值表。RT接地比接 AVREF 好,这样 NTC是对
地的。
R2、C1用于抗干扰的,因为 NTC 往往有引线,避免有干扰时出现异常。
R1的供电使用 ADC的参考电压,NTC测温是相对测量(也叫比例测量,相对
的,测量外部电压则为绝对测量),则测量结果跟 AVREF 的电压无关(即用 2.5V、
3V、4V、5V 结果都一样)。

测温方法常用两个:
1、查表法,根据厂家给的温度-阻值表格,用EXCEL重建建表,12位ADC 读
数=4096*RT(RT+RI),得到一个新的温度-ADC值表格,一般按1度的间隔做
表,0.1度可以线性插补获得,1度之内线性处理的误差可以忽略。查表则使用
二分法查表,快速。
2、直接计算方法。
NTC 热敏电阻温度计算公式:Rt=Ro*EXP(B/T-B/To),温度单位为绝对温度K,
开尔文。
截图202506201958219866.jpg
截图202506201959389488.jpg

截图202506201951485040.jpg










截图202506201945548570.jpg
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 5 天前 | 显示全部楼层
梁工补充:ADC应用NTC测温

R1 为参考电阻,要求精度 1%,RT 为负温度系数热敏电阻,标称值为25 度C
时的电阻值,厂家会提供温度阻值表。RT接地比接 AVREF 好,这样 NTC是对
地的。
R2、C1用于抗干扰的,因为 NTC 往往有引线,避免有干扰时出现异常。
R1的供电使用 ADC的参考电压,NTC测温是相对测量(也叫比例测量,相对
的,测量外部电压则为绝对测量),则测量结果跟 AVREF 的电压无关(即用 2.5V、
3V、4V、5V 结果都一样)。

测温方法常用两个:
1、查表法,根据厂家给的温度-阻值表格,用EXCEL重建建表,12位ADC 读
数=4096*RT(RT+RI),得到一个新的温度-ADC值表格,一般按1度的间隔做
表,0.1度可以线性插补获得,1度之内线性处理的误差可以忽略。查表则使用
二分法查表,快速。
2、直接计算方法。
NTC 热敏电阻温度计算公式:Rt=Ro*EXP(B/T-B/To),温度单位为绝对温度K,
开尔文。


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 5 天前 | 显示全部楼层
陈工补充:任务调度-通过定时器分时进行
void main(void)
{
    SYS_Init();

    while (1)
    {
        Task_Pro_Handler_Callback();
    }
}

static TASK_COMPONENTS Task_Comps[]=
{
//状态  计数  周期  函数
        {0, 1,   1,   Sample_Display},                /* task 1 Period: 1ms */
        {0, 10,  10,  Sample_MatrixKey},        /* task 2 Period: 10ms */
        {0, 10,  10,  Sample_adcKey},                /* task 3 Period: 10ms */
        {0, 300, 300, Sample_NTC},                        /* task 4 Period: 300ms */
        {0, 500, 500, Sample_RTC},                        /* task 5 Period: 500ms */
        /* Add new task here */
};

u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps[0]);

//========================================================================
// 函数: Task_Handler_Callback
// 描述: 任务标记回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2022-05-26
//========================================================================
void Task_Marks_Handler_Callback(void)
{
        u8 i;
        for(i=0; i<Tasks_Max; i++)
        {
                if(Task_Comps.TIMCount)    /* If the time is not 0 */
                {
                        Task_Comps.TIMCount--;  /* Time counter decrement */
                        if(Task_Comps.TIMCount == 0)  /* If time arrives */
                        {
                                /*Resume the timer value and try again */
                                Task_Comps.TIMCount = Task_Comps.TRITime;  
                                Task_Comps.Run = 1;    /* The task can be run */
                        }
                }
        }
}

//========================================================================
// 函数: Task_Pro_Handler_Callback
// 描述: 任务处理回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2022-05-26
//========================================================================
void Task_Pro_Handler_Callback(void)
{
        u8 i;
        for(i=0; i<Tasks_Max; i++)
        {
                if(Task_Comps.Run) /* If task can be run */
                {
                        Task_Comps.Run = 0;    /* Flag clear 0 */
                        Task_Comps.TaskHook();  /* Run task */
                }
        }
}

截图202506202253502847.jpg


截图202506202255255323.jpg




回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 前天 11:11 | 显示全部楼层
第三十五集 DMA简介,DMA模块的结构和主要特征一

一、DMA简介

1、DMA概述
DMA(Direct MemoryAccess,直接存储器存取)用来提供在
外设和存储器之间或者存储器和存储器之间的高速数据传输,无须
CPU干预,是所有现代计算机的重要特色。

2、DMA的基本工作过程
在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制
器来处理数据的传送,数据传送完毕再把信息反馈给CPU,这样在
很大程度上减轻了CPU资源占有率,可以大大节省系统资源。


3、DMA的应用场合
DMA主要用于快速设备和主存储器成批交换数据的场合。在这种
应用中,处理问题的出发点集中到两点:一是不能丢失快速设备提供
出来的数据,二是进一步减少快速设备输入输出操作过程中对CPU
的打扰。
这可以通过把这批数据的传输过程交由DMA来控制,让DMA代替
CPU控制在快速设备与主存储器之间直接传输数据。当完成一批数
据传输之后,快速设备还是要向CPU发一次中断请求,报告本次传
输结束的同时,“请示”下一步的操作要求。

二、DMA模块的结构和主要特征


STC8H8K64U单片机外设到外设的DMA关系矩阵
1
STC8H8K64U的DMA控制器用来管理来自于一个或多个外设对存储
访问的请求,支持如下几种DMA操作:
M2MDMA:XRAM存储器到XRAM存储器的数据读写。
ADC_DMA:自动扫描使能的ADC通道并将转换的ADC数据自动存储到XRAM中
SPI_DMA:自动将XRAM中的数据和SPI外设之间进行数据交换。
UR1T_DMA:自动将XRAM中的数据通过串口1发送出去。
UR1R_DMA:自动将串口1接收到的数据存储到XRAM中。
UR2T_DMA:自动将XRAM中的数据通过串口2发送出去。
UR2R_DMA:自动将串口2接收到的数据存储到XRAM中。
UR3T_DMA:自动将XRAM中的数据通过串口3发送出去。
UR3R_DMA:自动将串口3接收到的数据存储到XRAM中。
UR4TDMA:自动将XRAM中的数据通过串口4发送出去。
UR4R_DMA:自动将串口4接收到的数据存储到XRAM中。
LCMDMA:自动将XRAM中的数据和LCM设备之间进行数据交换。

截图202506231129307230.jpg

截图202506231130147864.jpg


DMA控制器和CPU共享系统数据总线,执行直接存储器数据传输。当CPU和DMA
同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线若干个
周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器
或外设)使用时间。
每个外设每次DMA数据传输的最大数据量为256字节,即最大缓冲区为256字节。
串口1接收每次可支持256字节、同时发送每次也可支持256字节,串口的发送和接
收不冲突。
串口2、串口3、串口4、SPI、LCM以及存储器直接的DMA均与串口1类似。
特别的,ADC的DMA数据传输计数方式不是最大数据传输量,而与ADC的使能通
道和ADC转换次数设置相关。



3、STC8H8K64U单片机DMA的处理过程
每种DMA对XRAM的读写操作都可设置4级访问优先级,硬件自动进行XRAM总线
的访问仲裁,不会影响CPU的XRAM的访问。
发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据访问优
先级处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给
外设一个应答信号。当外设从DMA控制器得到应答信号时,立即释放请求。一旦外设
释放了请求,DMA控制器同时撤销应答信号。如果有更多的请求,外设可以在下一个
周期启动请求。



DMA应用


外设之间的数据传输
DMA可以控制8种外设之间传输数据:
XRAM存储器到XRAM存储器的数据读写
自动扫描使能的ADC通道并将转换的ADC数据自动存储到XRAM中
XRAM存储器和SPI接口之间进行数据交换
XRAM存储器和串口1进行数据交换
XRAM存储器和串口2进行数据交换
XRAM存储器和串口3进行数据交换
XRAM存储器和串口4进行数据交换
XRAM存储器和LCM设备之间进行数据交换


实例讲解

截图202506231154072404.jpg


UR2TIE:UR2T_DMA中断使能控制位。
0:禁止UR2T_DMA中断;
1:允许UR2T_DMA中断。
UR2TIP[1:0]:UR2T_DMA中断优先级控制位。
00:最低级(0);01:较低级(1);10:较高级(2);11:最高级(3)。


UR2TPTY[1:0]:UR2T_DMA数据总线访问优先级控制位。
00:最低级(0);01:较低级(1);10:较高级(2);11:最高级(3)。


截图202506231202326935.jpg
截图202506231203298134.jpg

TXOVW:UR2T_DMA数据覆盖标志位。UR2T_DMA正在数据传输过程中,
串口写S2BUF寄存器再次触发串口发送数据时,会导致数据传输失败,此时
硬件自动将TXOVW置1。标志位需软件清零。


截图202506231205311310.jpg

截图202506231205595115.jpg
截图202506231209367317.jpg

截图202506231211191286.jpg

UR2RIE:UR2R_DMA中断使能控制位。
0:禁止UR2R_DMA中断;
1:允许UR2R_DMA中断。
UR2RIP[1:0]:UR2R_DMA中断优先级控制位。
00:最低级;01:较低级;10:较高级;11:最高级。
UR2RPTY[1:0]:UR2R_DMA数据总线访问优先级控制位。
00:最低级;
01:较低级;10:较高级;11:最高级。


截图202506231233558590.jpg

截图202506231236016193.jpg
截图202506231237014037.jpg
截图202506231237314226.jpg
配置过程

2、XRAM存储器和串口2进行数据交换的DMA设置过程
(1)选择串口2使用的引脚,并设置I/O工作模式为漏极开路模式。
(2)设置串口2的工作模式和波特率。
(3)进行DMA的设置,包括:设置传输总字节数、设置地址、中断允许控制
、启动DMA自动传送。
(4)开放CPU中断。
(5)在中断服务程序中,对相应的标志位进行处理。

截图202506231240598326.jpg

核心代码
P_SW2|=Ox80;//扩展寄存器(XFR)访问使能
P_SW2F=1;
//UART2切换到P4.6P4.7

截图202506231245505757.jpg
截图202506231246252824.jpg

截图202506231247299306.jpg
截图202506231248508558.jpg
截图202506231249443398.jpg
代码演示
截图202506231251438324.jpg


截图202506231254134787.jpg
截图202506231255318426.jpg


截图202506231256262414.jpg
截图202506231257023744.jpg



回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 前天 15:33 | 显示全部楼层
第三十六集 DMA简介,DMA模块的结构和主要特征二

DMA接收的程序实例

截图202506231533436798.jpg

截图202506231535304095.jpg

截图202506231537591203.jpg


截图202506231539258904.jpg

发送和接收的中断号是不一样的!

既又发送也又接收的实例


截图202506231616375311.jpg


截图202506231618015903.jpg


截图202506231618571823.jpg

截图202506231620399193.jpg

截图202506231621075942.jpg

二、利用DMA控制器实现ADC数据自动存储到XRAM中
1、实现ADC数据自动存储到XRAM中的DMA寄存器



截图202506231637526137.jpg


截图202506231639534041.jpg


截图202506231640572767.jpg


截图202506231643142557.jpg


截图202506231646265722.jpg

截图202506231647472834.jpg

2、利用DMA控制器实现ADC数据自动存储到XRAM中时的DMA设
置过程
(1)选择ADC通道使用的引脚,并设置I/O工作模式为高阻输入模式。
(2)ADC模块的初始化,包括数据格式、时间设置、速度设置等,并为ADC
模块上电。
(3)进行DMA的设置,包括:中断允许控制、ADC转换数据存储地址、每个
通道ADC转换次数、ADC通道使能、使能DMA、启动DMA。
(4)开放CPU中断。
(5)在中断服务程序中,对相应的标志位进行处理。


实例

截图202506231700111945.jpg

核心代码
截图202506231718271853.jpg

截图202506231719431423.jpg

截图202506231720499238.jpg

SPEED[3:0]:设置ADC工作时钟频率。
{FADC=SYSclk/2/(SPEED+1)}



截图202506231722576599.jpg

截图202506231724076429.jpg

CVTIMESEL[3:0]:设置进行ADC_DMA操作时,对每个ADC通道进行ADC
转换的次数。
0xxX:1次;
1000:2次;
1001:4次;
1010:8次
1011:16次;
1100:32次;
1101:64次;
1110:128次;
1111:256次。
截图202506231725512500.jpg


截图202506231726321376.jpg
代码示例

截图202506231732407875.jpg


截图202506231747098108.jpg

截图202506231750222291.jpg


截图202506231751183156.jpg



回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 前天 21:59 | 显示全部楼层
梁工补充ModBus从入门到实战
MODBUS协议是应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供主从机通信。
自从1979年出现工业串行链路的事实标准以来,MODBUS使成千上万的自动化设备能够通信。
MODBUS是一个请求(询问)/应答协议,并且提供功能码规定的服务。MODBUS协议是一个纯软
件协议,跟硬件无关,可以用于任意的可编程器件和PC(电脑)上。
MODBUS有两种传输模式:ASCII模式或RTU模式。一般都使用RTU模式,我就没用过ASCII模式)。

截图202506232159257858.jpg

截图202506232204472734.jpg
上面的进行修改

截图202506232207492154.jpg

截图202506232215525357.jpg



/*------------------------------------------------------------------*/
/* --- STC MCU International Limited -------------------------------*/
/* --- STC 1T Series MCU Demo --------------------------------------*/
/* --- Fax: 86-0513-55012956,55012947,55012969 ---------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966 ---------------------*/
/* --- Web: www.stcai.com ------------------------------------------*/
/* --- BBS: www.stcaimcu.com ---------------------------------------*/
/* If you want to use the program or the program referenced in the  */
/* article, please specify in which data and procedures from STC    */
/*------------------------------------------------------------------*/


/*********************************************************/
        #define MAIN_Fosc                11059200L        //定义主时钟

#include        "..\..\STC8Gxxx.h"


/*************        功能说明        **************

请先别修改程序, 直接下载"08-串口1中断收发-C语言-MODBUS协议"里的"UART1.hex"测试, 主频选择11.0592MHZ. 测试正常后再修改移植.

串口1按MODBUS-RTU协议通信. 本例为从机程序, 主机一般是电脑端.

本例程只支持多寄存器读和多寄存器写, 寄存器长度为64个, 别的命令用户可以根据需要按MODBUS-RTU协议自行添加.

本例子数据使用大端模式(与C51一致), CRC16使用小端模式(与PC一致).

默认参数:
串口1设置均为 1位起始位, 8位数据位, 1位停止位, 无校验.
串口1(P3.0 P3.1): 9600bps.

定时器0用于超时计时. 串口每收到一个字节都会重置超时计数, 当串口空闲超过35bit时间时(9600bps对应3.6ms)则接收完成.
用户修改波特率时注意要修改这个超时时间.

本例程只是一个应用例子, 科普MODBUS-RTU协议并不在本例子职责范围, 用户可以上网搜索相关协议文本参考.
本例定义了64个寄存器, 访问地址为0x1000~0x103f.
命令例子:
写入4个寄存器(8个字节):
10 10 1000 0004 08 1234 5678 90AB CDEF 4930
返回:
10 10 10 00 00 04 C64B
读出4个寄存器:
10 03 1000 0004 4388
返回:
10 03 08 12 34 56 78 90 AB CD EF D53D

命令错误返回信息(自定义):
0x90: 功能码错误. 收到了不支持的功能码.
0x91: 命令长度错误.
0x92: 写入或读出寄存器个数或字节数错误.
0x93: 寄存器地址错误.

注意: 收到广播地址0x00时要处理信息, 但不返回应答.

******************************************/

/*************        本地常量声明        **************/
#define        RX1_Length        128                /* 接收缓冲长度 */
#define        TX1_Length        128                /* 发送缓冲长度 */


/*************        本地变量声明        **************/
u8        xdata        RX1_Buffer[RX1_Length];        //接收缓冲
u8        xdata        TX1_Buffer[TX1_Length];        //发送缓冲

u8        RX1_cnt;                //接收字节计数.
u8        TX1_cnt;                //发送字节计数
u8        TX1_number;                //要发送的字节数
u8        RX1_TimeOut;        //接收超时计时器

bit        B_RX1_OK;                // 接收数据标志
bit        B_TX1_Busy;                // 发送忙标志


/*************        本地函数声明        **************/
void        UART1_config(u32 brt, u8 timer, u8 io);        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
u8                Timer0_Config(u8 t, u32 reload);        //t=0: reload值是主时钟周期数,  t=1: reload值是时间(单位us), 返回0正确, 返回1装载值过大错误.
u16                MODBUS_CRC16(u8 *p, u8 n);
u8                MODBUS_RTU(void);



#define        SL_ADDR                0x10        /* 本从机站号地址 */
#define        REG_ADDRESS        0x1000        /* 寄存器首地址   */
#define        REG_LENGTH        64                /* 寄存器长度     */
u16                xdata modbus_reg[REG_LENGTH];        /* 寄存器地址 */

//========================================================================
// 函数: void main(void)
// 描述: 主函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void main(void)
{
        u8        i;
        u16        crc;

        Timer0_Config(0, MAIN_Fosc / 10000);        //t=0: reload值是主时钟周期数,  (中断频率, 20000次/秒)
        UART1_config(9600UL, 1, 0);        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.

        EA = 1;

        while (1)
        {
                if(B_RX1_OK && !B_TX1_Busy)        //收到数据, 进行MODBUS-RTU协议解析
                {
                        if(MODBUS_CRC16(RX1_Buffer, RX1_cnt) == 0)        //首先判断CRC16是否正确, 不正确则忽略, 不处理也不返回信息
                        {
                                if((RX1_Buffer[0] == 0x00) || (RX1_Buffer[0] == SL_ADDR))        //然后判断站号地址是否正确, 或者是否广播地址(不返回信息)
                                {
                                        if(RX1_cnt > 2) RX1_cnt -= 2;        //去掉CRC16校验字节
                                        i = MODBUS_RTU();        //MODBUS-RTU协议解析
                                        if(i != 0)        //错误处理
                                        {
                                                TX1_Buffer[0] = SL_ADDR;        //站号地址
                                                TX1_Buffer[1] = i;                        //错误代码
                                                crc = MODBUS_CRC16(TX1_Buffer, 2);
                                                TX1_Buffer[2] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                                                TX1_Buffer[3] = (u8)(crc>>8);
                                                B_TX1_Busy = 1;                //标志发送忙
                                                TX1_cnt    = 0;                //发送字节计数
                                                TX1_number = 4;                //要发送的字节数
                                                TI = 1;                                //启动发送
                                        }
                                }
                        }
                        RX1_cnt = 0;
                        B_RX1_OK = 0;
                }
        }
}


/****************************** MODBUS_CRC (shift) *************** past test 06-11-27 *********
        计算CRC,调用方式        MODBUS_CRC16(&CRC,8);        &CRC为首地址,8为字节数
        CRC-16 for MODBUS
        CRC16=X16+X15+X2+1
        TEST: ---> ABCDEFGHIJ        CRC16=0x0BEE        1627T
*/
//========================================================================
// 函数: u16        MODBUS_CRC16(u8 *p, u8 n)
// 描述: 计算CRC16函数.
// 参数: *p: 要计算的数据指针.
//        n: 要计算的字节数.
// 返回: CRC16值.
// 版本: V1.0, 2022-3-18 梁工
//========================================================================
u16        MODBUS_CRC16(u8 *p, u8 n)
{
        u8        i;
        u16        crc16;

        crc16 = 0xffff;        //预置16位CRC寄存器为0xffff(即全为1)
        do
        {
                crc16 ^= (u16)*p;                //把8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器
                for(i=0; i<8; i++)                //8位数据
                {
                        if(crc16 & 1)        crc16 = (crc16 >> 1) ^ 0xA001;        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位,
                                                                                                                        //再异或多项式0xA001
                        else        crc16 >>= 1;                                                        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位
                }
                p++;
        }while(--n != 0);
        return        (crc16);
}

/********************* modbus协议 *************************/
/***************************************************************************
写多寄存器
数据:          地址    功能码   寄存地址 寄存器个数  写入字节数   写入数据   CRC16
偏移:     0        1        2 3      4 5          6          7~        最后2字节
字节:   1 byte   1 byte    2 byte   2 byte      1byte       2*n byte   2 byte
         addr     0x10      xxxx     xxxx        xx         xx....xx    xxxx

  返回
数据:          地址    功能码   寄存地址 寄存器个数   CRC16
偏移:     0        1        2 3      4 5         6 7
字节:   1 byte   1 byte    2 byte   2 byte      2 byte
         addr     0x10      xxxx     xxxx        xxxx


读多寄存器
数据:站号(地址)  功能码   寄存地址 寄存器个数  CRC16
偏移:      0       1        2 3      4 5         6 7
字节:    1 byte  1 byte    2 byte   2 byte     2 byte
          addr    0x03      xxxx     xxxx       xxxx

  返回
数据:站号(地址)  功能码   读出字节数  读出数据  CRC16
偏移:      0       1        2           3~      最后2字节
字节:   1 byte   1 byte    1byte      2*n byte  2 byte
         addr     0x03       xx       xx....xx   xxxx

  返回错误代码
数据:站号(地址)  错误码   CRC16
偏移:      0       1      最后2字节
字节:   1 byte   1 byte   2 byte
         addr     0x93     xxxx
***************************************************************************/
u8        MODBUS_RTU(void)
{
        u8        i,j,k;
        u16        reg_addr;        //寄存器地址
        u8        reg_len;        //写入寄存器个数
        u16        crc;

        if(RX1_Buffer[1] == 0x10)        //写多寄存器
        {
                if(RX1_cnt < 9)                return 0x91;                //命令长度错误
                if((RX1_Buffer[4] != 0) || ((RX1_Buffer[5] *2) != RX1_Buffer[6]))        return 0x92;        //写入寄存器个数与字节数错误
                if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //写入寄存器个数错误

                reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                reg_len = RX1_Buffer[5];        //写入寄存器个数
                if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误
                if((reg_len*2+7) != RX1_cnt)        return 0x91;        //命令长度错误

                j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                for(k=7, i=0; i<reg_len; i++,j++)
                {
                        modbus_reg[j] = ((u16)RX1_Buffer[k] << 8) + RX1_Buffer[k+1];        //写入数据, 大端模式
                        k += 2;
                }

                if(RX1_Buffer[0] != 0)        //非广播地址则应答
                {
                        for(i=0; i<6; i++)        TX1_Buffer = RX1_Buffer;        //要返回的应答
                        crc = MODBUS_CRC16(TX1_Buffer, 6);
                        TX1_Buffer[6] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                        TX1_Buffer[7] = (u8)(crc>>8);
                        B_TX1_Busy = 1;                //标志发送忙
                        TX1_cnt    = 0;                //发送字节计数
                        TX1_number = 8;                //要发送的字节数
                        TI = 1;                                //启动发送
                }
        }
        else if(RX1_Buffer[1] == 0x03)        //读多寄存器
        {
                if(RX1_Buffer[0] != 0)        //非广播地址则应答
                {
                        if(RX1_cnt != 6)                return 0x91;                //命令长度错误
                        if(RX1_Buffer[4] != 0)        return 0x92;        //读出寄存器个数错误
                        if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //读出寄存器个数错误

                        reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                        reg_len = RX1_Buffer[5];        //读出寄存器个数
                        if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                        if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误

                        j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                        TX1_Buffer[0] = SL_ADDR;        //站号地址
                        TX1_Buffer[1] = 0x03;                //读功能码
                        TX1_Buffer[2] = reg_len*2;        //返回字节数

                        for(k=3, i=0; i<reg_len; i++,j++)
                        {
                                TX1_Buffer[k++] = (u8)(modbus_reg[j] >> 8);        //数据为大端模式
                                TX1_Buffer[k++] = (u8)modbus_reg[j];
                        }
                        crc = MODBUS_CRC16(TX1_Buffer, k);
                        TX1_Buffer[k++] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                        TX1_Buffer[k++] = (u8)(crc>>8);
                        B_TX1_Busy = 1;                //标志发送忙
                        TX1_cnt    = 0;                //发送字节计数
                        TX1_number = k;                //要发送的字节数
                        TI = 1;                                //启动发送
                }
        }
        else        return 0x90;        //功能码错误

        return 0;        //解析正确
}


//========================================================================
// 函数:u8        Timer0_Config(u8 t, u32 reload)
// 描述: timer0初始化函数.
// 参数:      t: 重装值类型, 0表示重装的是系统时钟数, 其余值表示重装的是时间(us).
//       reload: 重装值.
// 返回: 0: 初始化正确, 1: 重装值过大, 初始化错误.
// 版本: V1.0, 2018-3-5
//========================================================================
u8        Timer0_Config(u8 t, u32 reload)        //t=0: reload值是主时钟周期数,  t=1: reload值是时间(单位us)
{
        TR0 = 0;        //停止计数

        if(t != 0)        reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL);        //重装的是时间(us), 计算所需要的系统时钟数.
        if(reload >= (65536UL * 12))        return 1;        //值过大, 返回错误
        if(reload < 65536UL)        AUXR |= 0x80;                //1T mode
        else
        {
                AUXR &= ~0x80;        //12T mode
                reload = reload / 12;
        }
        reload = 65536UL - reload;
        TH0 = (u8)(reload >> 8);
        TL0 = (u8)(reload);

        ET0 = 1;        //允许中断
        TMOD &= 0xf0;
        TMOD |= 0;        //工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
        TR0 = 1;                        //开始运行
        return 0;
}

//========================================================================
// 函数: void timer0_ISR (void) interrupt TIMER0_VECTOR
// 描述:  timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2016-5-12
//========================================================================
void timer0_ISR (void) interrupt TIMER0_VECTOR
{
        if(RX1_TimeOut != 0)
        {
                if(--RX1_TimeOut == 0)        //超时
                {
                        if(RX1_cnt != 0)        //接收有数据
                        {
                                B_RX1_OK = 1;        //标志已收到数据块
                        }
                }
        }
}


//========================================================================
// 函数: SetTimer2Baudraye(u16 dat)
// 描述: 设置Timer2做波特率发生器。
// 参数: dat: Timer2的重装值.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void        SetTimer2Baudraye(u16 dat)        // 选择波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
{
        AUXR &= ~(1<<4);        //Timer stop
        AUXR &= ~(1<<3);        //Timer2 set As Timer
        AUXR |=  (1<<2);        //Timer2 set as 1T mode
        TH2 = (u8)(dat >> 8);
        TL2 = (u8)dat;
        IE2  &= ~(1<<2);        //禁止中断
        AUXR |=  (1<<4);        //Timer run enable
}


//========================================================================
// 函数: void        UART1_config(u32 brt, u8 timer, u8 io)
// 描述: UART1初始化函数。
// 参数:   brt: 通信波特率.
//       timer: 波特率使用的定时器, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率.
//          io: 串口1切换到的IO,  io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void        UART1_config(u32 brt, u8 timer, u8 io)        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
{
        brt = 65536UL - (MAIN_Fosc / 4) / brt;
        if(timer == 2)        //波特率使用定时器2
        {
                AUXR |= 0x01;                //S1 BRT Use Timer2;
                SetTimer2Baudraye((u16)brt);
        }

        else                //波特率使用定时器1
        {
                TR1 = 0;
                AUXR &= ~0x01;                //S1 BRT Use Timer1;
                AUXR |=  (1<<6);        //Timer1 set as 1T mode
                TMOD &= ~(1<<6);        //Timer1 set As Timer
                TMOD &= ~0x30;                //Timer1_16bitAutoReload;
                TH1 = (u8)(brt >> 8);
                TL1 = (u8)brt;
                ET1 = 0;                        // 禁止Timer1中断
                INT_CLKO &= ~0x02;        // Timer1不输出高速时钟
                TR1  = 1;                        // 运行Timer1
        }

                 if(io == 1)        {S1_USE_P36P37();        P3n_standard(0xc0);}        //切换到 P3.6 P3.7
        else if(io == 2)        {S1_USE_P16P17();        P1n_standard(0xc0);}        //切换到 P1.6 P1.7
        else if(io == 3)        {S1_USE_P43P44();        P4n_standard(0x18);}        //切换到 P4.3 P4.4
        else                                {S1_USE_P30P31();        P3n_standard(0x03);}        //切换到 P3.0 P3.1

        SCON = (SCON & 0x3f) | (1<<6);        // 8位数据, 1位起始位, 1位停止位, 无校验
//        PS  = 1;        //高优先级中断
        ES  = 1;        //允许中断
        REN = 1;        //允许接收
}


//========================================================================
// 函数: void UART1_ISR (void) interrupt UART1_VECTOR
// 描述: 串口1中断函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void UART1_ISR (void) interrupt UART1_VECTOR
{
        if(RI)
        {
                RI = 0;
                if(!B_RX1_OK)        //接收缓冲空闲
                {
                        if(RX1_cnt >= RX1_Length)        RX1_cnt = 0;
                        RX1_Buffer[RX1_cnt++] = SBUF;
                        RX1_TimeOut = 36;        //接收超时计时器, 35个位时间
                }
        }

        if(TI)
        {
                TI = 0;
                if(TX1_number != 0)        //有数据要发
                {
                        SBUF = TX1_Buffer[TX1_cnt++];
                        TX1_number--;
                }
                else        B_TX1_Busy = 0;
        }
}


















截图202506232217037479.jpg
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:38
  • 最近打卡:2025-06-25 10:45:14
已绑定手机

4

主题

40

回帖

246

积分

中级会员

积分
246
发表于 前天 23:33 | 显示全部楼层
陈工补充串口库函数应用

截图202506232333455264.jpg


/*------------------------------------------------------------------*/
/* --- STC MCU International Limited -------------------------------*/
/* --- STC 1T Series MCU Demo --------------------------------------*/
/* --- Fax: 86-0513-55012956,55012947,55012969 ---------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966 ---------------------*/
/* --- Web: www.stcai.com ------------------------------------------*/
/* --- BBS: www.stcaimcu.com ---------------------------------------*/
/* If you want to use the program or the program referenced in the  */
/* article, please specify in which data and procedures from STC    */
/*------------------------------------------------------------------*/


/*********************************************************/
        #define MAIN_Fosc                11059200L        //定义主时钟

#include        "..\..\STC8Gxxx.h"


/*************        功能说明        **************

请先别修改程序, 直接下载"08-串口1中断收发-C语言-MODBUS协议"里的"UART1.hex"测试, 主频选择11.0592MHZ. 测试正常后再修改移植.

串口1按MODBUS-RTU协议通信. 本例为从机程序, 主机一般是电脑端.

本例程只支持多寄存器读和多寄存器写, 寄存器长度为64个, 别的命令用户可以根据需要按MODBUS-RTU协议自行添加.

本例子数据使用大端模式(与C51一致), CRC16使用小端模式(与PC一致).

默认参数:
串口1设置均为 1位起始位, 8位数据位, 1位停止位, 无校验.
串口1(P3.0 P3.1): 9600bps.

定时器0用于超时计时. 串口每收到一个字节都会重置超时计数, 当串口空闲超过35bit时间时(9600bps对应3.6ms)则接收完成.
用户修改波特率时注意要修改这个超时时间.

本例程只是一个应用例子, 科普MODBUS-RTU协议并不在本例子职责范围, 用户可以上网搜索相关协议文本参考.
本例定义了64个寄存器, 访问地址为0x1000~0x103f.
命令例子:
写入4个寄存器(8个字节):
10 10 1000 0004 08 1234 5678 90AB CDEF 4930
返回:
10 10 10 00 00 04 C64B
读出4个寄存器:
10 03 1000 0004 4388
返回:
10 03 08 12 34 56 78 90 AB CD EF D53D

命令错误返回信息(自定义):
0x90: 功能码错误. 收到了不支持的功能码.
0x91: 命令长度错误.
0x92: 写入或读出寄存器个数或字节数错误.
0x93: 寄存器地址错误.

注意: 收到广播地址0x00时要处理信息, 但不返回应答.

******************************************/

/*************        本地常量声明        **************/
#define        RX1_Length        128                /* 接收缓冲长度 */
#define        TX1_Length        128                /* 发送缓冲长度 */


/*************        本地变量声明        **************/
u8        xdata        RX1_Buffer[RX1_Length];        //接收缓冲
u8        xdata        TX1_Buffer[TX1_Length];        //发送缓冲

u8        RX1_cnt;                //接收字节计数.
u8        TX1_cnt;                //发送字节计数
u8        TX1_number;                //要发送的字节数
u8        RX1_TimeOut;        //接收超时计时器

bit        B_RX1_OK;                // 接收数据标志
bit        B_TX1_Busy;                // 发送忙标志


/*************        本地函数声明        **************/
void        UART1_config(u32 brt, u8 timer, u8 io);        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
u8                Timer0_Config(u8 t, u32 reload);        //t=0: reload值是主时钟周期数,  t=1: reload值是时间(单位us), 返回0正确, 返回1装载值过大错误.
u16                MODBUS_CRC16(u8 *p, u8 n);
u8                MODBUS_RTU(void);



#define        SL_ADDR                0x10        /* 本从机站号地址 */
#define        REG_ADDRESS        0x1000        /* 寄存器首地址   */
#define        REG_LENGTH        64                /* 寄存器长度     */
u16                xdata modbus_reg[REG_LENGTH];        /* 寄存器地址 */

//========================================================================
// 函数: void main(void)
// 描述: 主函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void main(void)
{
        u8        i;
        u16        crc;

        Timer0_Config(0, MAIN_Fosc / 10000);        //t=0: reload值是主时钟周期数,  (中断频率, 20000次/秒)
        UART1_config(9600UL, 1, 0);        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.

        EA = 1;

        while (1)
        {
                if(B_RX1_OK && !B_TX1_Busy)        //收到数据, 进行MODBUS-RTU协议解析
                {
                        if(MODBUS_CRC16(RX1_Buffer, RX1_cnt) == 0)        //首先判断CRC16是否正确, 不正确则忽略, 不处理也不返回信息
                        {
                                if((RX1_Buffer[0] == 0x00) || (RX1_Buffer[0] == SL_ADDR))        //然后判断站号地址是否正确, 或者是否广播地址(不返回信息)
                                {
                                        if(RX1_cnt > 2) RX1_cnt -= 2;        //去掉CRC16校验字节
                                        i = MODBUS_RTU();        //MODBUS-RTU协议解析
                                        if(i != 0)        //错误处理
                                        {
                                                TX1_Buffer[0] = SL_ADDR;        //站号地址
                                                TX1_Buffer[1] = i;                        //错误代码
                                                crc = MODBUS_CRC16(TX1_Buffer, 2);
                                                TX1_Buffer[2] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                                                TX1_Buffer[3] = (u8)(crc>>8);
                                                B_TX1_Busy = 1;                //标志发送忙
                                                TX1_cnt    = 0;                //发送字节计数
                                                TX1_number = 4;                //要发送的字节数
                                                TI = 1;                                //启动发送
                                        }
                                }
                        }
                        RX1_cnt = 0;
                        B_RX1_OK = 0;
                }
        }
}


/****************************** MODBUS_CRC (shift) *************** past test 06-11-27 *********
        计算CRC,调用方式        MODBUS_CRC16(&CRC,8);        &CRC为首地址,8为字节数
        CRC-16 for MODBUS
        CRC16=X16+X15+X2+1
        TEST: ---> ABCDEFGHIJ        CRC16=0x0BEE        1627T
*/
//========================================================================
// 函数: u16        MODBUS_CRC16(u8 *p, u8 n)
// 描述: 计算CRC16函数.
// 参数: *p: 要计算的数据指针.
//        n: 要计算的字节数.
// 返回: CRC16值.
// 版本: V1.0, 2022-3-18 梁工
//========================================================================
u16        MODBUS_CRC16(u8 *p, u8 n)
{
        u8        i;
        u16        crc16;

        crc16 = 0xffff;        //预置16位CRC寄存器为0xffff(即全为1)
        do
        {
                crc16 ^= (u16)*p;                //把8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器
                for(i=0; i<8; i++)                //8位数据
                {
                        if(crc16 & 1)        crc16 = (crc16 >> 1) ^ 0xA001;        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位,
                                                                                                                        //再异或多项式0xA001
                        else        crc16 >>= 1;                                                        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位
                }
                p++;
        }while(--n != 0);
        return        (crc16);
}

/********************* modbus协议 *************************/
/***************************************************************************
写多寄存器
数据:          地址    功能码   寄存地址 寄存器个数  写入字节数   写入数据   CRC16
偏移:     0        1        2 3      4 5          6          7~        最后2字节
字节:   1 byte   1 byte    2 byte   2 byte      1byte       2*n byte   2 byte
         addr     0x10      xxxx     xxxx        xx         xx....xx    xxxx

  返回
数据:          地址    功能码   寄存地址 寄存器个数   CRC16
偏移:     0        1        2 3      4 5         6 7
字节:   1 byte   1 byte    2 byte   2 byte      2 byte
         addr     0x10      xxxx     xxxx        xxxx


读多寄存器
数据:站号(地址)  功能码   寄存地址 寄存器个数  CRC16
偏移:      0       1        2 3      4 5         6 7
字节:    1 byte  1 byte    2 byte   2 byte     2 byte
          addr    0x03      xxxx     xxxx       xxxx

  返回
数据:站号(地址)  功能码   读出字节数  读出数据  CRC16
偏移:      0       1        2           3~      最后2字节
字节:   1 byte   1 byte    1byte      2*n byte  2 byte
         addr     0x03       xx       xx....xx   xxxx

  返回错误代码
数据:站号(地址)  错误码   CRC16
偏移:      0       1      最后2字节
字节:   1 byte   1 byte   2 byte
         addr     0x93     xxxx
***************************************************************************/
u8        MODBUS_RTU(void)
{
        u8        i,j,k;
        u16        reg_addr;        //寄存器地址
        u8        reg_len;        //写入寄存器个数
        u16        crc;

        if(RX1_Buffer[1] == 0x10)        //写多寄存器
        {
                if(RX1_cnt < 9)                return 0x91;                //命令长度错误
                if((RX1_Buffer[4] != 0) || ((RX1_Buffer[5] *2) != RX1_Buffer[6]))        return 0x92;        //写入寄存器个数与字节数错误
                if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //写入寄存器个数错误

                reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                reg_len = RX1_Buffer[5];        //写入寄存器个数
                if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误
                if((reg_len*2+7) != RX1_cnt)        return 0x91;        //命令长度错误

                j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                for(k=7, i=0; i<reg_len; i++,j++)
                {
                        modbus_reg[j] = ((u16)RX1_Buffer[k] << 8) + RX1_Buffer[k+1];        //写入数据, 大端模式
                        k += 2;
                }

                if(RX1_Buffer[0] != 0)        //非广播地址则应答
                {
                        for(i=0; i<6; i++)        TX1_Buffer = RX1_Buffer;        //要返回的应答
                        crc = MODBUS_CRC16(TX1_Buffer, 6);
                        TX1_Buffer[6] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                        TX1_Buffer[7] = (u8)(crc>>8);
                        B_TX1_Busy = 1;                //标志发送忙
                        TX1_cnt    = 0;                //发送字节计数
                        TX1_number = 8;                //要发送的字节数
                        TI = 1;                                //启动发送
                }
        }
        else if(RX1_Buffer[1] == 0x03)        //读多寄存器
        {
                if(RX1_Buffer[0] != 0)        //非广播地址则应答
                {
                        if(RX1_cnt != 6)                return 0x91;                //命令长度错误
                        if(RX1_Buffer[4] != 0)        return 0x92;        //读出寄存器个数错误
                        if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //读出寄存器个数错误

                        reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                        reg_len = RX1_Buffer[5];        //读出寄存器个数
                        if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                        if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误

                        j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                        TX1_Buffer[0] = SL_ADDR;        //站号地址
                        TX1_Buffer[1] = 0x03;                //读功能码
                        TX1_Buffer[2] = reg_len*2;        //返回字节数

                        for(k=3, i=0; i<reg_len; i++,j++)
                        {
                                TX1_Buffer[k++] = (u8)(modbus_reg[j] >> 8);        //数据为大端模式
                                TX1_Buffer[k++] = (u8)modbus_reg[j];
                        }
                        crc = MODBUS_CRC16(TX1_Buffer, k);
                        TX1_Buffer[k++] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                        TX1_Buffer[k++] = (u8)(crc>>8);
                        B_TX1_Busy = 1;                //标志发送忙
                        TX1_cnt    = 0;                //发送字节计数
                        TX1_number = k;                //要发送的字节数
                        TI = 1;                                //启动发送
                }
        }
        else        return 0x90;        //功能码错误

        return 0;        //解析正确
}


//========================================================================
// 函数:u8        Timer0_Config(u8 t, u32 reload)
// 描述: timer0初始化函数.
// 参数:      t: 重装值类型, 0表示重装的是系统时钟数, 其余值表示重装的是时间(us).
//       reload: 重装值.
// 返回: 0: 初始化正确, 1: 重装值过大, 初始化错误.
// 版本: V1.0, 2018-3-5
//========================================================================
u8        Timer0_Config(u8 t, u32 reload)        //t=0: reload值是主时钟周期数,  t=1: reload值是时间(单位us)
{
        TR0 = 0;        //停止计数

        if(t != 0)        reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL);        //重装的是时间(us), 计算所需要的系统时钟数.
        if(reload >= (65536UL * 12))        return 1;        //值过大, 返回错误
        if(reload < 65536UL)        AUXR |= 0x80;                //1T mode
        else
        {
                AUXR &= ~0x80;        //12T mode
                reload = reload / 12;
        }
        reload = 65536UL - reload;
        TH0 = (u8)(reload >> 8);
        TL0 = (u8)(reload);

        ET0 = 1;        //允许中断
        TMOD &= 0xf0;
        TMOD |= 0;        //工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
        TR0 = 1;                        //开始运行
        return 0;
}

//========================================================================
// 函数: void timer0_ISR (void) interrupt TIMER0_VECTOR
// 描述:  timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2016-5-12
//========================================================================
void timer0_ISR (void) interrupt TIMER0_VECTOR
{
        if(RX1_TimeOut != 0)
        {
                if(--RX1_TimeOut == 0)        //超时
                {
                        if(RX1_cnt != 0)        //接收有数据
                        {
                                B_RX1_OK = 1;        //标志已收到数据块
                        }
                }
        }
}


//========================================================================
// 函数: SetTimer2Baudraye(u16 dat)
// 描述: 设置Timer2做波特率发生器。
// 参数: dat: Timer2的重装值.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void        SetTimer2Baudraye(u16 dat)        // 选择波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
{
        AUXR &= ~(1<<4);        //Timer stop
        AUXR &= ~(1<<3);        //Timer2 set As Timer
        AUXR |=  (1<<2);        //Timer2 set as 1T mode
        TH2 = (u8)(dat >> 8);
        TL2 = (u8)dat;
        IE2  &= ~(1<<2);        //禁止中断
        AUXR |=  (1<<4);        //Timer run enable
}


//========================================================================
// 函数: void        UART1_config(u32 brt, u8 timer, u8 io)
// 描述: UART1初始化函数。
// 参数:   brt: 通信波特率.
//       timer: 波特率使用的定时器, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率.
//          io: 串口1切换到的IO,  io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void        UART1_config(u32 brt, u8 timer, u8 io)        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
{
        brt = 65536UL - (MAIN_Fosc / 4) / brt;
        if(timer == 2)        //波特率使用定时器2
        {
                AUXR |= 0x01;                //S1 BRT Use Timer2;
                SetTimer2Baudraye((u16)brt);
        }

        else                //波特率使用定时器1
        {
                TR1 = 0;
                AUXR &= ~0x01;                //S1 BRT Use Timer1;
                AUXR |=  (1<<6);        //Timer1 set as 1T mode
                TMOD &= ~(1<<6);        //Timer1 set As Timer
                TMOD &= ~0x30;                //Timer1_16bitAutoReload;
                TH1 = (u8)(brt >> 8);
                TL1 = (u8)brt;
                ET1 = 0;                        // 禁止Timer1中断
                INT_CLKO &= ~0x02;        // Timer1不输出高速时钟
                TR1  = 1;                        // 运行Timer1
        }

                 if(io == 1)        {S1_USE_P36P37();        P3n_standard(0xc0);}        //切换到 P3.6 P3.7
        else if(io == 2)        {S1_USE_P16P17();        P1n_standard(0xc0);}        //切换到 P1.6 P1.7
        else if(io == 3)        {S1_USE_P43P44();        P4n_standard(0x18);}        //切换到 P4.3 P4.4
        else                                {S1_USE_P30P31();        P3n_standard(0x03);}        //切换到 P3.0 P3.1

        SCON = (SCON & 0x3f) | (1<<6);        // 8位数据, 1位起始位, 1位停止位, 无校验
//        PS  = 1;        //高优先级中断
        ES  = 1;        //允许中断
        REN = 1;        //允许接收
}


//========================================================================
// 函数: void UART1_ISR (void) interrupt UART1_VECTOR
// 描述: 串口1中断函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void UART1_ISR (void) interrupt UART1_VECTOR
{
        if(RI)
        {
                RI = 0;
                if(!B_RX1_OK)        //接收缓冲空闲
                {
                        if(RX1_cnt >= RX1_Length)        RX1_cnt = 0;
                        RX1_Buffer[RX1_cnt++] = SBUF;
                        RX1_TimeOut = 36;        //接收超时计时器, 35个位时间
                }
        }

        if(TI)
        {
                TI = 0;
                if(TX1_number != 0)        //有数据要发
                {
                        SBUF = TX1_Buffer[TX1_cnt++];
                        TX1_number--;
                }
                else        B_TX1_Busy = 0;
        }
}


回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-6-25 12:18 , Processed in 0.157124 second(s), 100 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表