网站首页 > 法规 >打印时死机用友u8(用友无法打印)

打印时死机用友u8(用友无法打印)

  由于新冠疫情的影响,全球供应链受到了极大的影响,众多芯片价格更是一涨再涨,以出货量最大,应用面最广的stm32f103系列为例,价格由正常价格的7元左右一片涨到了80多元一片,价格整整涨了十多倍。更严重的问题是,你很可能面临着,即使有钱也拿不到货的境地,供货周期长达三个月,这无论是在bom成本上还是交付上都是无法接受的。正是由于这样的困难,国产的芯片迎来了波替换潮,对于一些要求不高的嵌入式应用场合,stc系列51单片机,由于其极低的价格、稳定的货源,成为不错的选择之一。

stm32f103c8与stc8a8k64d4对比主要资源对比

STM32F103V8

STC8A8K64D4

GPIO

37pin

43pin

主频

72M

45M

flash

64K

64K

RAM

20K

8K

UART

3

4

ADC

10 ch,10位

15ch, 12位

定时器

1tick + 4个32位

4个

乘除法指令

单周期硬件乘除法

价格

64元

4.5元

最重要的是: STC有现货

   针对stc8a8k64d4的硬件资源,由于其没有硬件乘除法,导致其在需要进行大量数据计算的应用场景上效率低下,对于一般的应用场景,stc在硬件资源上完全够用,在现有的供应链条件下,STC是一个非常不错的选择。

  俗话说有利就有弊,STC带来了不少的硬件成本优势,但是其在编程上却带来了一些不适应,我们在代码编写时,需要特别注意。

  由于stc系列单片机是基于51架构,虽然它在内核上进行了加强,但是由于时代的局限性,51架构和现代的MCU架构相比,存在不少的差距。由于现代编译器不断的演进,我们大部人都是使用C语言进行编程,这在一定程度上,帮助我们屏蔽了架构层面的巨大差异。而且由于架构的落后,再加宏晶公司与ST公司本身在IP上面的技术差距,这导致我们在使用STC51的外设时,也有很大的差异。下图是51的架构和stm32的架构,可以看出明显的差距。


STC8A使用心得


STC8A使用心得


编程模型的区别 -- 函数重入

  对于C语言的编程模型(请参看我前面的文章《C语言代码组成 - BSS、Data、Stack、Heap、Code、Const》),51和ARM在处理stack上,有巨大的区别。51加架构中,sp指针指向的区域是pdata区域,这个区域最大情况只有256个字节,作为栈来说,是不可能用来分配局部变量的。因此keil编译器对于临时变量,采用了一种叫作变量覆盖的技术,即多个函数使用同一区域作作为临时变量区,下一个函数调用运行时,会将上一个函数运行的临时变量覆盖掉。这样就解决了栈空间不足的问题。而且因为没有进出栈的操作,也提高了代码效率。但这带来的最大的问题就是函数重入问题,即51代码全部是不可重入的,如果一个函数在中断中被调用,那就意味着这个函数必须是可重入的,才能正常运行,而且这个中断函数所调用的下级函数也必须是可重入的。如何解决重入问题:

  1. keil通过模拟堆栈技术,可以实现函数的重入,通过增加关键字 reentrant 将一个函数定义为可重入函数;
  2. 将需要重入的函数定义为原子操作,即进入该函数时,禁止全局中断,执行完成后,开启全局中断;
  3. 中断函数中不调用其它函数,只是置标志,进行变量操作,然后再在main的主循环中查询标志,再执行相应的操作;

以上三种方法,各有缺点,方法一简单粗爆,需要消耗更多的资源;方法二会导致中断响应不及时;方法三导致中断响应不及时。所以用户需要根据项目特点,使用合适的策略;

函数传参

  C51通过寄存器传递参数,最多只能传递三个参数


STC8A使用心得


  1. 对于三个或者三个以内的参数,使用寄存器传参;
  2. 对于多于三个的参数,可以定义为xdata变量进行传参;
  3. 将函数定义为可重入函数使用栈进行传参数;

乘除法

  由于51没有硬件乘除法,而且是8位机,对于除法指令,十分耗时,这会导致程序响应慢。对于除法指令,尽量使用移位操作来代替。

调试问题

  STC51是没有像arm那样的jtag调试接口,下载程序和调试是通过串口进行的,但是在使用串口进行单步调试时,非常不稳定,经常通讯不上,导致调试失败。在实际项目中,我是使用uart打印log来进行调试。由于打印的字符串是定义在常量区,过多的打印会导致代码量增大,所以要合理的使用打印。

串口驱动优化

  由于STC51的串口只有发送完成中断和接受完成中断,而没有接受空闲中断,这就需要我们使用特殊的处理方式,具体方法可以参见我前面的文章《串口接收中的帧同步》。代码如下

//收发数据的数据结构typedef struct{     u8  id;             //串口号    u8  TX_read;        //发送读指针    u8  TX_write;       //发送写指针    u8  B_TX_busy;      //忙标志    u8  RX_Cnt;         //接收字节计数    u8  RX_TimeOut;     //接收超时    u8  B_RX_OK;        //接收块完成} COMx_Define; u8  xdata TX1_Buffer[COM_TX1_Lenth];    //发送缓冲u8  xdata RX1_Buffer[COM_RX1_Lenth];    //接收缓冲//定时器中断//用来判断帧超时, 在1ms中断中调用void Serial_HookMs(void){    if(COM1.RX_TimeOut > 0){        COM1.RX_TimeOut --;    } else {        //if(COM1.RX_Cnt > 0){            //COM1.B_RX_OK = 1;        //}    }}//中断函数,发送中断和接受中断void UART1_ISR_Handler (void) interrupt UART1_VECTOR{    if(RI){        RI = 0;        if(COM1.RX_Cnt >= COM_RX1_Lenth)    COM1.RX_Cnt = 0;        RX1_Buffer[COM1.RX_Cnt++] = SBUF;        COM1.RX_TimeOut = TimeOutSet1;    }    if(TI){        TI = 0;        if(COM1.TX_read != COM1.TX_write){            SBUF = TX1_Buffer[COM1.TX_read];            if(++COM1.TX_read >= COM_TX1_Lenth)                 COM1.TX_read = 0;        }        else                COM1.B_TX_busy = 0;    }}int Serial_Read(UART_Port_t port, uint8_t* pmsg, int msg_size){    COMx_Define *com = NULL;    int len = 0;    uint8_t *rbuff = NULL;    switch(port){        case UART1:            com = &COM1;            rbuff = RX1_Buffer;            break;        default:            return -1;    }    if((com->RX_TimeOut == 0) && (com->RX_Cnt > 0)){        if(msg_size < com->RX_Cnt){            return -2;        }        len = com->RX_Cnt;        memcpy(pmsg, rbuff, len);        com->RX_Cnt = 0;        return len;    }    return -3;}//采用中断发送int Serial_Write(UART_Port_t port, uint8_t* pmsg, int len){    COMx_Define *com = NULL;    uint8_t *sbuff = NULL;    int i;    int s_buff_max = 0;    void (* uart_send)(u8 dat);        switch(port){        case UART1:            com = &COM1;            sbuff = TX1_Buffer;            uart_send = TX1_write2buff;            s_buff_max = COM_TX1_Lenth;            break;        default:            return -1;    }    if(com->B_TX_busy != 0){        //发送中        return -2;    }    //剩余空间不够    if((s_buff_max - (com->TX_write + s_buff_max - com->TX_read) % s_buff_max) < len){        return -3;    }    //将数据考贝到缓冲区中;    for(i = 1; i < len; i ++){        sbuff[com->TX_write] = pmsg[i];        com->TX_write ++;        com->TX_write %= s_buff_max;    }    uart_send(pmsg[0]);    return len;}

使用串口的收发中断以避免阻塞式的收发,用定时器实现帧间隔判断来判断是否是完整一帧,这样极大的增加了程序的效率。

IIC驱动库的坑

  在项目中,使用STC8A的IIC操作外设时,新焊接的板子,会出现不断复位重启的现象,通过加打印定位程序是卡死在iic操作中,通过分析程序,在iic的驱动中存在等应答的操作,这是一个死循环,如下:

void Wait(void){    while (!(I2CMSST & 0x40));    I2CMSST &= ~0x40;}

  由于新焊接的板子,存在虚焊的问题,导到IIC应答失败,这就使IIC永远 收不到应答,从而死在wait中,看门狗无法喂狗,导致复位。将驱动改为如下:

void Wait(void){    u32 i = 0;    while (!(I2CMSST & 0x40)){        i ++;        if(i > 250){            break;        }    }    I2CMSST &= ~0x40;}

通过增加超时机制,来避免永远无法应答的问题。

ADC的坑

  在项目中,使用ADC采集变送器的电压信号,当ADC采集到的值大于某个值时,输出开关信号,实际中,ADC采集是在主循环中做的,主循环约为1ms,当突然接入电压信号时,差不多要3~4ms,才会输出开关信号,存在很大的时延。按理来说,时延应该在一个循环周期以内,即1ms以内才是正常的。而通过通讯口读取出来的稳定值是正确的。最后通过打印ADC转换值,才发现ADC的值是有一个较缓的上升过程,这是由于ADC的输入电阻和采样电容较大导致的,具体的请参看前面的文章《AD转换中的采样电容和输入电阻》。即使通过调整参数,增加采样时间,依然无法解决此问题,最后解决的办法是连续多次转换(多次测试后为4次),取最后一次的结果作为效值,从而解决此问题。

数据类型

  由于C51是8位单片机,其数据类型长度与ARM相比还是很大区别的,比如int在keil c51中,默认是 16位的。如果在调用外部第三方库时,稍不注意,很容易出现数据溢出。具体的可以参看stc8a库中的type_def.h文件。

  由于STC51的外设功能相对简单,而且STC的示例完成度很高,使用起来还是很方便,从32位机转到51,在前期会有一个磨合的过程,由于编译器的错误提示功能的强大,再加上网上资源的丰富,一般的问题,基本都能很快解决。总的来说,对于一些适合的应用场景,STC的51单片机是个不错的选择。

相关内容

打印时死机用友u8(用友无法打印)文档下载.: PDF DOC TXT
您可能感兴趣的文章
24h快讯
违法生育包括哪些情形,什么叫违法生育
一、教师如果违法计划生育会有什么处罚如果是教师,违反计划生育规定,应由计生机......
2024-04-05 法规
自残违法吗?自残算不算犯罪
一、教唆他人自残构成犯罪吗教唆他人自残有故意杀人的嫌疑,所以规劝大家不要以身......
2024-04-05 法规
新昌机动车违法?机动车违章处理时间限制
一、新昌货车通行证怎么办理1.办理新昌货车通行证是可行的。2.因为新昌货车通......
2024-04-05 法规
违法举报网站,公安干警违法举报平台
一、国家有奖举报平台以举报违章车辆为例,有奖举报平台如下:为加大监管力度,营......
2024-04-05 法规
查看更多