求助STC8G1K17-20PIN工作在22.1184MHz的时候串口1收发出错
本帖最后由 mangolu 于 2021-3-21 20:40 编辑STC8G1K17-20PIN,用串口助手发送数据到Uart1,然后返回。工作在11.0592MHz的时候,收发正常,但是工作在22.1184MHz的时候,接收到返回的数据隔一段时间是错误的,请大家帮分析下这是什么原因造成的:
这里是工作在22.1184MHz的时候,返回数据错误:
这里是程序下载报告:
正在检测目标单片机 ...
单片机型号: STC8G1K17-20/16PIN
固件版本号: 7.3.12U
当前芯片的硬件选项为:
. 内部IRC振荡器的频率: 22.109MHz
. 掉电唤醒定时器的频率: 35.050KHz
. 振荡器放大增益使能
. P3.2和P3.3与下次下载无关
. 上电复位时增加额外的复位延时
. 复位引脚用作普通I/O口
. 检测到低压时复位
. 低压检测门槛电压 : 2.00 V
. 上电复位时,硬件不启动内部看门狗
. 上电自动启动内部看门狗时的预分频数为 : 256
. 空闲状态时看门狗定时器停止计数
. 启动看门狗后,软件可以修改分频数,但不能关闭看门狗
. 下次下载用户程序时,将用户EEPROM区一并擦除
. 下次下载用户程序时,没有相关的端口控制485
. 下次下载时不需要校验下载口令
. 内部参考电压: 1188 mV (参考范围: 1100~1300mV)
. 内部安排测试时间: 2021年1月29日
单片机型号: STC8G1K17-20/16PIN
固件版本号: 7.3.12U
开始调节频率 ...
调节后的频率: 22.123MHz (0.022%)
正在重新握手 ... 成功
当前的波特率: 115200
正在擦除目标区域 ... 完成 !
芯片出厂序列号 : F757C5AD162396
正在下载用户代码 ... 完成 !
正在设置硬件选项 ... 完成 !
更新后的硬件选项为:
. 内部IRC振荡器的频率: 22.123MHz
. 掉电唤醒定时器的频率: 35.050KHz
. 振荡器放大增益使能
. P3.2和P3.3与下次下载无关
. 上电复位时增加额外的复位延时
. 复位引脚用作普通I/O口
. 检测到低压时复位
. 低压检测门槛电压 : 2.00 V
. 上电复位时,硬件不启动内部看门狗
. 上电自动启动内部看门狗时的预分频数为 : 256
. 空闲状态时看门狗定时器停止计数
. 启动看门狗后,软件可以修改分频数,但不能关闭看门狗
. 下次下载用户程序时,将用户EEPROM区一并擦除
. 下次下载用户程序时,没有相关的端口控制485
. 下次下载时不需要校验下载口令
. 内部参考电压: 1188 mV (参考范围: 1100~1300mV)
. 内部安排测试时间: 2021年1月29日
. 芯片出厂序列号 : F757C5AD162396
单片机型号: STC8G1K17-20/16PIN
固件版本号: 7.3.12U
. 用户设定频率: 22.118MHz
. 调节后的频率: 22.123MHz
. 频率调节误差: 0.022%
操作成功 !(2021-03-21 20:17:53)
在这里请教下,多次下载会发现:
. 调节后的频率: 22.123MHz
. 频率调节误差: 0.022%
这个频率和误差有时会变化,这个是什么情况?
这里是工作在11.0592MHz,所有数据是正常的,并且经过长时间运行是没有任何问题的:
这里是程序下载报告:
. 低压检测门槛电压 : 2.00 V
. 上电复位时,硬件不启动内部看门狗
. 上电自动启动内部看门狗时的预分频数为 : 256
. 空闲状态时看门狗定时器停止计数
. 启动看门狗后,软件可以修改分频数,但不能关闭看门狗
. 下次下载用户程序时,将用户EEPROM区一并擦除
. 下次下载用户程序时,没有相关的端口控制485
. 下次下载时不需要校验下载口令
. 内部参考电压: 1188 mV (参考范围: 1100~1300mV)
. 内部安排测试时间: 2021年1月29日
单片机型号: STC8G1K17-20/16PIN
固件版本号: 7.3.12U
开始调节频率 ...
调节后的频率: 11.054MHz (-0.043%)
正在重新握手 ... 成功
当前的波特率: 115200
正在擦除目标区域 ... 完成 !
芯片出厂序列号 : F757C5AD162396
正在下载用户代码 ... 完成 !
正在设置硬件选项 ... 完成 !
更新后的硬件选项为:
. 内部IRC振荡器的频率: 11.054MHz
. 掉电唤醒定时器的频率: 35.050KHz
. 振荡器放大增益使能
. P3.2和P3.3与下次下载无关
. 上电复位时增加额外的复位延时
. 复位引脚用作普通I/O口
. 检测到低压时复位
. 低压检测门槛电压 : 2.00 V
. 上电复位时,硬件不启动内部看门狗
. 上电自动启动内部看门狗时的预分频数为 : 256
. 空闲状态时看门狗定时器停止计数
. 启动看门狗后,软件可以修改分频数,但不能关闭看门狗
. 下次下载用户程序时,将用户EEPROM区一并擦除
. 下次下载用户程序时,没有相关的端口控制485
. 下次下载时不需要校验下载口令
. 内部参考电压: 1188 mV (参考范围: 1100~1300mV)
. 内部安排测试时间: 2021年1月29日
. 芯片出厂序列号 : F757C5AD162396
单片机型号: STC8G1K17-20/16PIN
固件版本号: 7.3.12U
. 用户设定频率: 11.059MHz
. 调节后的频率: 11.054MHz
. 频率调节误差: -0.043%
操作成功 !(2021-03-21 20:22:00)
下面是代码,初始化定时器是用官方软件生成的,这个发送接收代码在其他单片机上没有发现有问题:
#include "stdint.h"
#include "common.h"
#include "config.h"
#include "initial.h"
#include "version.h"
#include "STC8xxxx.H"
/** 定义串口缓存大小 */
#define UART_BUFFER_SIZE 64
uint8_t idata u8Uart_Buffer; // 串口缓存
uint8_t u8Front = 0; // 读指针
uint8_t u8Rear = 0; // 写指针
uint8_t u8Count = 0; // 缓存存储字节计数
uint8_t u8TX_State; // 发送状态,0表示不在发送状态,1表示在发送状态
void Uart1_Init_1(void);
void Uart1_Init_2(void);
void Uart_Send_Start(void);
void main(void) {
/** 初始化端口 */
P3M0 = 0x00; // 准双向口
P3M1 = 0x00;
/** 初始化串口 */
Uart1_Init_1();
/** 设置串口开始工作 */
ES = 1; // 开启串口1中断
EA = 1; // 开启全局中断
while(1) {
Uart_Send_Start();
}
}
/** 串口初始化函数 */
void Uart1_Init_1(void) { //115200bps@11.0592MHz
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0xE8; //设定定时初值
TH1 = 0xFF; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/** 串口初始化函数 */
void Uart1_Init_2(void) { //115200bps@22.1184MHz
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0xD0; //设定定时初值
TH1 = 0xFF; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/** 串口发送数据,使用中断发送,要发送一个数据以产生中断 */
void Uart_Send_Start(void) {
if(u8Count > 63) {
// 出错处理
} else {
if((u8Count > 0) && (u8TX_State == 0)) { // 缓存有数据,并且不在发送状态,此时要进入发送状态
SBUF = u8Uart_Buffer;
u8Count --;
u8Front ++;
u8TX_State = 1;
}
}
}
/** 串口1中断服务函数 */
void Uart_ISR(void) interrupt UART1_VECTOR {
if(RI == 1) { // 接收中断
RI = 0; // 清接收中断,必需手动清除
/** 接收中断表示数据接收到,取串口缓存寄存器的值 */
u8Uart_Buffer = SBUF;
u8Rear ++;
u8Count ++;
u8Rear &= 0x3F; // 0-63
}
if(TI == 1) { // 发送中断
TI = 0; // 清发送中断,必需手动清除
/** 发送中断表示数据发送完成 */
/** 发送中断表示数据发送完成,清忙标志 */
if(u8Count > 0) {
SBUF = u8Uart_Buffer;
u8Count --;
u8Front ++;
u8Front &= 0x3F; // 0-63
} else {
u8TX_State = 0;
}
}
}
工作在11.0592MHz和22.1184MHz只是修改了串口初始化的定时器初值,并在下载软件界面更改工作频率,其他的代码没有动到,为什么工作在22.1184MHz时返回的数据会出现错误呢? 楼主可以测试STC官网的例程,或者电话给STC技术支持给一个按你要求编写的例程。 接收中断里干的活太多了。 饭桶 发表于 2021-3-21 21:52
接收中断里干的活太多了。
同样是115200通讯速率,11.0592m频率正常,反倒22.1184m不正常,这个说不过去啊。并且中断里就执行那几步,别的比如modbus还要计算crc16校验,这个任务更重吧? 小李非刀 发表于 2021-3-21 21:48
楼主可以测试STC官网的例程,或者电话给STC技术支持给一个按你要求编写的例程。 ...
官网的在别的型号上试过,晚点我看看。
我疑惑的是更改工作频率就不正常,我这个程序在别家51工作是正常的。 这是属于典型的临界变量问题。是代码的BUG.
本帖最后由 mangolu 于 2021-3-22 04:00 编辑
su33691 发表于 2021-3-22 03:44
这是属于典型的临界变量问题。是代码的BUG.
测试了官方的例程确实没有问题。我这里代码有什么问题,能否指点一下?谢谢!
我这个收发程序就是之前请教你们的,在N76E003上是没有问题的。 一个或多个对象被中断和主循环同时访问,可能无法确保数据的完整性和可用性。
临界变量问题是编程的痛点。
楼主不是依靠编程来养家糊口,慢慢修炼吧。 mangolu 发表于 2021-3-21 23:21
同样是115200通讯速率,11.0592m频率正常,反倒22.1184m不正常,这个说不过去啊。并且中断里就执行那几步 ...
不管什么计算,包括MODBUS的,哪能在中断里做? 不要在循环里面进行发送工作。
SBUF = u8Uart_Buffer; 只在中断里填充。
在主循环里只填充发送缓冲区就可以。
这情况属于运行速度太快引起的 程序结构不合理 临界问题。 我之前在STC8F系列上,串口接收数据的中断函数里,除了接收字符串,还有接收到字符串后进行判断,并调用对应函数,工作频率6MHZ。
后来,我把这个程序移植到STC16上面,频率升到了24M或32M,发现非常容易出错,表现为死机,特别是长字符串时。鼓捣几天都不能解决。最后,我就把判断字符串和调用对应函数的部分,放到大循环里,中断函数里只保留接收字符串,就改善了。 饭桶 发表于 2021-3-22 07:32
不管什么计算,包括MODBUS的,哪能在中断里做?
哦,不好意思,是我搞错了,确实不是在中断中计算的。但是我这几步处理应该不多吧? su33691 发表于 2021-3-22 05:49
一个或多个对象被中断和主循环同时访问,可能无法确保数据的完整性和可用性。
临界变量问题是编程的痛点。
...
在这里缓存并不存在主循环和中断同时处的情况吧? mPiDDR 发表于 2021-3-22 08:00
不要在循环里面进行发送工作。
SBUF = u8Uart_Buffer; 只在中断里填充。
在主循环里只填充发送缓冲区就可 ...
主循环和中断发送里通过u8TX_State变量判断,不存在两个同时发的情况吧? 这里是STC官方例程代码,在中断里分写读写收送缓存,然后在主循环中收缓存写入发缓存,这里中断不影响主循环里的操作吗?
void main(void)
{
u8 i;
GPIO_config();
UART_config();
EA = 1;
while (1)
{
delay_ms(1);
if(COM1.RX_TimeOut > 0) //超时计数
{
if(--COM1.RX_TimeOut == 0)
{
if(COM1.RX_Cnt > 0)
{
for(i=0; i<COM1.RX_Cnt; i++) TX1_write2buff(RX1_Buffer); //收到的数据原样返回
}
COM1.RX_Cnt = 0;
}
}
}
}
void UART1_int (void) interrupt UART1_VECTOR
{
if(RI)
{
RI = 0;
if(COM1.B_RX_OK == 0)
{
if(COM1.RX_Cnt >= COM_RX1_Lenth) COM1.RX_Cnt = 0;
RX1_Buffer = SBUF;
COM1.RX_TimeOut = TimeOutSet1;
}
}
if(TI)
{
TI = 0;
if(COM1.TX_read != COM1.TX_write)
{
SBUF = TX1_Buffer;
if(++COM1.TX_read >= COM_TX1_Lenth) COM1.TX_read = 0;
}
else COM1.B_TX_busy = 0;
}
} mPiDDR 发表于 2021-3-22 08:00
不要在循环里面进行发送工作。
SBUF = u8Uart_Buffer; 只在中断里填充。
在主循环里只填充发送缓冲区就可 ...
你这个是对的,经过把主循环里发送判断改成:
/** 串口发送数据 */
void Uart_Send_Start(void) {
if(u8Count > 63) {
// 出错处理
} else {
if((u8Count > 0) && (u8TX_State == 0)) { // 缓存有数据,并且不在发送状态,此时要进入发送状态
u8TX_State = 1; // 设置为发送状态
TI = 1; // 产生一个发送中断以进入发送状态
}
}
}
主循环不进行任何发送操作,有数据要发送,只产生一个发送中断,现在测试一切正常。
今天测试刚刚买到的STC8G1K17A/SOP8,原来的程序和硬件使用的是STC15F101W,只用了4个IO,查看了GPIO设置,完全一样,更换了头文件,重新编译,程序代码都是一样的字节数,在串口下载界面上看十六进制代码完全一样,烧录也是设置一样的时钟,结果诡异的事情发生了.................
现在的STC8G1K,速度比15F的快不少,我的代码初始GPIO,主循环就是不断改变GPIO的高低电平,然后中间插入nop延时,没有任何其他的计算和外设硬件的设置应用,一模一样的代码,一模一样的时钟,但是输出速度快了大概30%,奇怪得很{:titter:}
好在应用要求不需要很准确,所以先用着吧{:lol:},估计这2款STC51实际内核不一样了,虽然都叫STC ilikemcu 发表于 2021-3-22 13:54
今天测试刚刚买到的STC8G1K17A/SOP8,原来的程序和硬件使用的是STC15F101W,只用了4个IO,查看了GPIO设置, ...
之前看过,好像跟15F系列有几个寄存器名称不一样
页:
[1]