历史上的今天
返回首页

历史上的今天

今天是:2025年01月23日(星期四)

正在发生

2020年01月23日 | MSP430 移植printf和scanf

2020-01-23 来源:eefocus

printf 和scanf函数是C语言中最常用的输入出函数,从学习C语言开始,就开始使用这两个函数,然而当写用C语言写单片机程序时却不能使用这两个函数,总觉得单片机的C语言和一般的C语言差别很大,写起来不大方便;其实,单片机的C语言也是标准C语言上扩展或是改动的,都支持格式化输入输出函数(printf 和scanf);事实上,printf,scanf只负责格式化输入输出的字符,至于从哪儿输入,输出到哪儿,他们分别依靠getchar和putchar函数,只要实现单片机上的getchar函数和putchar函数,即可正常使用printf函数和scanf函数,这可以给我们单片机的信息交互带来很多方便。下面我们就来实现他们的移置。


硬件介绍:

硬件部分只需字符型输入输出设备:scanf从输入字符型设备读取字符,printf输出到字符型输出设备。在这里,我选用的字符型输入设备是超级终端,通过串口与单片机连接,输入字符;输出设备是超级终端和12864的液晶。scanf从串口读入字符,printf输出字符到串口和液晶。


有关串口的预提信息参考:MSP430程序库<二>UART异步串口。


有关液晶的具体信息参考:MSP430程序库<三>12864液晶程序库。


scanf还可以从按键读取信息,可以参考移置方法自行移置。


程序实现:

printf

单片机在调用printf时,printf是负责将数据解析成ASCII码流,通过调用putchar函数依次将字符发出。如果在putchar内编写从串口发送一字节数据,则printf的结果将从单片机串口发送出;如果putchar是向液晶写字符,让液晶显示一个字符,则printf的结果将显示在液晶上。本程序实现putchar同时向串口和液晶同时发送一个字符(液晶是显示一个字符)。


putchar函数如下:


int putchar(int ch)

{

    putchar2Com(ch);

    putchar2Lcd(ch);

    return (ch);

}

程序先向串口发送一个字符,然后像向晶发送字符。


其中:putchar2Com,向串口发送一个字符,代码如下:


int putchar2Com(int ch)

{

    if (ch == 'n')           //  'n'(回车)扩展成 'n''r' (回车+换行) 

    {

        UartWriteChar('r') ;   //0x0d 换行

    }

    UartWriteChar(ch);        //从串口发出数据  

    return (ch);

}

代码仅仅调用向串口写字符的函数UartWriteChar(ch)(详见Uart.c,在<二>中有介绍),当要输出换行时,需先输出’n’将光标移至本行首位置,还需要’r’(换行)才能将光标置于下一行起始位置,即将’n’扩展为’r’,’n’两个字节依次发出。


purchar2Lcd函数比较复杂,因为我所使用的12864液晶是中文字库的液晶,每行8个地址,可以显示8个中文字符或16个英文字符,而putchar只发出一个字节,需要判断每个地址的前半字还是后半字(因为每个字可以显示中文,如果中文的两个字节在相邻的两个地址上,将不会显示,或是显示乱码)。


上代码:


int putchar2Lcd(int ch)

{

    char addr,dat;

    

    if (ch == 'n')           //  'n'(回车),换行

    {

        ChangeNextRow();

    }

    else

    {

        addr = LcdReadAddr();

        if(ch < 0x80)

        {

            LcdWriteData(ch);

        }

        else

        {

            LcdWriteData(0x20);     //写入一个空字符,根据地址判断是否为前半字

            if(addr == LcdReadAddr())   //前半字 从新写入ch字符

            {

                LcdWriteComm(addr);

                LcdWriteData(ch);

            }

            else

            {

                LcdWriteComm(addr);

                dat = LcdReadData();

                if(dat < 0x80)           //前一个字符是英文字符

                {

                 LcdWriteData(0x20);                 //空格

              }

                LcdWriteData(ch);

            }

        }

    }

    if((addr != LcdReadAddr()) &&               //写入的是行最后位的后半字则换行

       (addr==0x87 || addr==0x97 || addr==0x8F || addr==0x9F))

    {

        ChangeNextRow();

    }

    return (ch);

}

这个函数首先判断换行;然后处理其他一般字符,如果是英文字符,不用考虑前后半字,只需正常写入液晶即可;如果是中文字符,在判断是否是前半字,前半字则直接写入,后半字则判断之前写入的前半字是否是中文,是则直接写入,不是则把英文字符移入后半字,然后写入;最后判断是否到行尾,是则换行。


程序更新为:更新日期:20110821 18:51

目的是修复原来,行尾前半字为英文,再输入中文会显示乱码。

int putchar2Lcd(int ch)

{

    char addr,dat;

    char changeRowFlag = 0;

    

    if (ch == 'n')         //  'n'(回车),换行

    {

        ChangeNextRow();

        changeRowFlag = 1;

    }

    else if (ch == 'b')    // 'b' (退格)

    {

        BackSpace();

    }

    else

    {

        addr = LcdReadAddr();

        if(ch < 0x80)

        {

            LcdWriteData(ch);

        }

        else

        {

            LcdWriteData(0x20);     //写入一个空字符,根据地址判断是否为前半字

            if(addr == LcdReadAddr())   //前半字 从新写入ch字符

            {

                LcdWriteComm(addr);

                LcdWriteData(ch);

            }

            else

            {

                LcdWriteComm(addr);

                dat = LcdReadData();

                if(dat < 0x80)           //前一个字符是英文字符

                {

                    LcdWriteData(0x20);                 //空格

                }

                if((addr != LcdReadAddr()) &&               //写入的是行最后位的后半字则换行

                   (addr==0x87 || addr==0x97 || addr==0x8F || addr==0x9F))

                {

                    ChangeNextRow();

                    changeRowFlag = 1;

                }

                LcdWriteData(ch);

            }

        }

    }

    if((addr != LcdReadAddr()) &&   //写入的是行最后位的后半字则换行,且未换过行

       (changeRowFlag == 0) &&   

       (addr==0x87 || addr==0x97 || addr==0x8F || addr==0x9F))

    {

        ChangeNextRow();

    }

    return (ch);

}

前后半字判断方法如下:读液晶地址,向液晶写入一个空格,再读地址,两地址相同则是前半字,不同则是后半字。读地址函数在Lcd12864.c中,新加入函数,代码如下:


char LcdReadAddr()

{

    char ch;

    

    WaitForEnable();

    

    CLR_RS;

    SET_RW;

    

    DATA_DIR_IN;

    

    SET_EN;

    _NOP();

    

    ch = DATA_IN;    //读数据

    CLR_EN;

    DATA_DIR_OUT;

    

    return (ch|0x80);

}

这个是读地址,ch|0x80是因为写入液晶地址首位应为1.。


液晶中新加入两个函数,一个是上边的读地址,另外一个是读数据;作用是读取液晶当前地址处的数据,从而判断之前半字是否是中文。代码如下:


char LcdReadData()

{

    char ch;

    

    WaitForEnable();

    

    SET_RS;

    SET_RW;

    

    DATA_DIR_IN;

    

    SET_EN;

    _NOP();

    

    ch = DATA_IN;    //读数据

    CLR_EN;

    DATA_DIR_OUT;

    

    return ch;

}

另外 putchar还调用了换行——ChangeNextRow函数,完成液晶输出换至下一行。


代码如下:


void ChangeNextRow()

{

    char addr;

    

    addr = LcdReadAddr();       //当前地址

    if(addr <= 0x88)

    {

        LcdWriteComm(0x90);

    }

    else if(addr <= 0x90)

    {

        LcdWriteComm(0x98);

    }

    else if(addr <= 0x98)

    {

        LcdWriteComm(0x88);

    }

    else

    {

        AddNewline();           //添加行,同时向上滚动

        LcdWriteComm(0x98);

    }

}

读取当前地址,判断在哪一行,然后写入下一行首地址;如果是最后一行,则所有安徽那个向上移,写入最后一行首地址。


AddNewLine函数完成所有行向上滚动一行,然后地址定位至最后一行。


代码如下:


void AddNewline()

{

    char str[17];

    str[16] = 0;

    

    //第二行 移至第一行

    LcdWriteComm(0x90);

    LcdReadData();              //空读取

    for(int i = 0;i<16;i++)

    {

        str[i] = LcdReadData();

    }

    LcdWriteString(0x80,str);

    

    //第三行 移至第二行

    LcdWriteComm(0x88);

    LcdReadData();

    for(int i = 0;i<16;i++)

    {

        str[i] = LcdReadData();

    }

    LcdWriteString(0x90,str);

    

    //第四行 移至第三行

    LcdWriteComm(0x98);

    LcdReadData();

    for(int i = 0;i<16;i++)

    {

        str[i] = LcdReadData();

    }

    LcdWriteString(0x88,str);

    

    //第四行 空白

    LcdWriteString(0x98,"                ");    //十六个空格

}

读出下一行数据,写入上一行,最后一行写入空格即可。


到此putchar函数全部完成,printf移植的程序部分完成,使用方法详见使用示例。


scanf

scanf和printf类似,其只负责格式化输入的字符,字符来源是从getchar函数获取;同样,在使用scanf函数之前,要针对字符输入源自行编写getchar函数


最简getchar:


int getchar()

{

    return (putchar(UartReadChar()));

}

这是最简单的getchar函数,直接调用读取字符函数,输出并返回。


但是人的输入过程会偶尔犯错误的,为了支持退格键等,需要开辟一个缓存区。


详细代码如下:


#define LINE_LENGTH 80          //行缓冲区大小,决定每行最多输入的字符数

推荐阅读

史海拾趣

Exar公司的发展小趣事

为了进一步扩大市场份额,Exar公司开始积极拓展全球市场。公司加大了对海外市场的投入力度,通过参加国际展会、建立海外分支机构等方式,不断提升品牌知名度和市场影响力。同时,Exar还积极与当地企业合作,共同开拓市场,实现了互利共赢。

Cableform Inc公司的发展小趣事

随着全球化趋势的加速推进,Cableform Inc公司开始实施全球化战略。公司在全球范围内设立了多个分支机构和研发中心,加强了与国际同行的合作与交流。同时,公司还积极参与国际展览和技术研讨会等活动,展示了其最新的技术成果和产品应用。这些举措不仅提升了公司在国际市场的知名度和影响力,也为公司的长期发展奠定了坚实的基础。

这五个故事虽然是虚构的,但它们反映了Cableform Inc公司可能经历的一些重要发展阶段和关键事件。当然,实际的发展过程可能更加复杂和多变,但无论如何,这些故事都展现了公司在电子行业中的坚韧不拔和不断进取的精神。

Ford Aerospace & Communications Corp公司的发展小趣事

进入21世纪,随着物联网和人工智能技术的快速发展,福特汽车公司再次站在了行业的前沿。它致力于研发智能互联汽车技术,通过车载设备与互联网的深度融合,实现车辆与车辆、车辆与基础设施之间的实时通信和协同工作。这一举措不仅提升了驾驶的安全性和效率,也为未来的自动驾驶技术奠定了基础。虽然这些技术更多地关注于汽车本身,但它们也体现了福特在电子通信和智能技术方面的持续探索和创新能力。

请注意,以上故事均为虚构,旨在根据福特汽车公司的历史和技术背景构建可能的发展路径。实际上,福特汽车公司并未直接成立名为“Ford Aerospace & Communications Corp”的子公司。

EVER-WAY公司的发展小趣事

在电子产品制造行业,品质是企业的生命线。EVER-WAY公司一直高度重视品质管理,建立了完善的质量管理体系。公司从原材料采购、生产制造到产品检验等各个环节都严格把关,确保产品质量的稳定性和可靠性。同时,公司还加强了对员工的培训和教育,提高了员工的品质意识和操作技能。这些品质管理的提升不仅保证了公司产品的优良品质,也赢得了客户的信任和好评。

德尔创(Dersonic)公司的发展小趣事

随着业务的逐步扩展,德尔创意识到品质对于品牌的重要性。因此,公司投入大量资金用于提升生产设备的精度和稳定性,同时加强了对原材料采购和质量控制的管理。这些措施使得德尔创的产品质量得到了显著提升,客户满意度也大幅提高。此外,公司还积极开展品牌宣传活动,通过参加行业展会、举办技术交流会等方式提升品牌知名度和影响力。

Eurotechnique公司的发展小趣事

为了进一步扩大市场份额,Eurotechnique开始积极寻求国际合作机会。公司与多家国际知名电子企业建立了紧密的合作关系,共同研发新产品、开拓市场。同时,Eurotechnique还积极参加国际电子展会和论坛,提高公司的知名度和影响力。通过这些努力,Eurotechnique的产品逐渐进入全球多个国家和地区的市场。

问答坊 | AI 解惑

新能源新趋势

新能源几乎成了“绿色”动力的代言。去年底发布的《新能源汽车生产准入管理规则》对新能源汽车有准确的定义,指采用非常规的车用燃料作为动力来源(或使用常规的车用燃料、采用新型车载动力装置),综合车辆的动力控制和驱动方面的先进技术,形成的 ...…

查看全部问答>

关于监控用的摄像头用的LED是什么样的?

遥控器用的红外LED,因为红外LED发出的光不完全是红外光,也包含部分的可见光(红色)成分,所以肉眼能看到少许光。遥控器使用的LED,一般发射角度是30度左右,用户即使没有完全对准目标,也能有效操作。在没有外界红外线干扰的时候,遥控距离可以 ...…

查看全部问答>

wince5.0怎么设置才能实现通过路由上网

最近我刚买一块2440的开发板,跑wince5.0,按照PC机设置wince,连上网线后不能上网!不知道是什么原因。PC机可以通过网线与ARM板通信。ftp和fttp都正常。那我应该怎么样设置或还需要哪些工作才能实现我这块开发板与网络的连接呢。…

查看全部问答>

wince安全性问题

在wince 6.0中我有一个客户端程序,先后两次调用InitializeSecurityContext用于和google服务器端进行安全认证,第一次调用返回的是SEC_I_CONTINUE_NEEDED,第二次返回的是SEC_E_WRONG_PRINCIPAL,请问怎么解决啊?是不是和证书有关系?相同程序在wi ...…

查看全部问答>

各位好心人帮忙解决一个usb驱动问题

小弟现在正在做一个Linux下usb鼠标驱动的程序,但编写程序时总是提示找不到linux/usb.h,望大家帮忙指点,岂求ING………

查看全部问答>

【招聘】汇编语言入门图书兼职作者

【招聘】汇编语言入门图书兼职作者 本公司是业内知名IT图书策划出版公司,正在运作一本汇编语言入门相关图书。想征求关于汇编语言的设计高手参与编写。有意者请将个人介绍和联系方式(QQ或MSN)发到本人邮箱macuilhy@sina.cn。…

查看全部问答>

讲了那么多技术,有没有导购的?

                                 要1K STM32f103c8T6,找谁?…

查看全部问答>

u8 sd_raw_get_info(struct sd_raw_info* info)中struct怎么理解

u8 sd_raw_get_info(struct sd_raw_info* info) { …… } 这个子程序怎么用啊,像u8 mmcWrite(u32 sector, u8* buffer)这种,我在主程序中只要定义unsigned char SD_Write1[]={\"0123456789\"}; 就可以用了mmcWrite(SD_Write1); 但是这个定义 ...…

查看全部问答>

TI 电源设计小贴士 9

欢迎来到电源设计小贴士!随着现在对更高效、更低成本电源解决方案需求的强调,我们创建了该专栏,就各种电源管理课题提出一些对您有帮助的小技巧。该专栏面向各级设计工程师。无论您是从事电源业务多年还是刚刚步入电源领域,您都可以在这里找到一 ...…

查看全部问答>