前言
printf,scanf只负责格式化输入输出的字符,他们分别依靠getchar和putchar函数,只要实现在单片机上的getchar函数和putchar函数,并且头文件包含stdio.h
即可正常使用printf函数和scanf函数。
第一步,配置UART及初始化模块
/*
*UART模块初始化函数
*/
void Uart_Init(void)
{
//-----开启IO口的TXD和RXD功能-----
P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD
P1SEL2 = BIT1 + BIT2;
UCA0CTL1 |= UCSWRST;
//-----设置UART时钟源为ACLK-----
UCA0CTL1 |= UCSSEL_2; // CLK = SMCLK
//-----配置波特率参数9600bps-----
UCA0BR0 = 0x41;
UCA0BR1 = 0x03;
UCA0MCTL = 0x00;
//复位及开中断
UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt
__bis_SR_register(GIE);
}
第二步,编写基本收发函数
/*
*发送一个字符/数字 八位-一个字节
*参数:__Char:字符
*/
void Send_Char(uint8 Char)
{
while(!(IFG2 & UCA0TXIFG));//SBUFF为空时触发标志位执行下面语句
UCA0TXBUF=Char;
}
/*
*发送一个字符串
*参数:__Str:字符
*/
void Send_String(uchar *Str)
{
uchar i=0;
while(Str[i]!='\0')
{
Send_Char(Str[i]);
i++;
}
Send_Char(Str[i]);
}
/*
*接收一个字符
*/
uchar get_char(void)
{
__bis_SR_register(LPM0_bits); //进入低功耗等待接受中断发生
return Receive_Data; //返回接收中断返回的数据
}
对于接收采用中断方式
//响应Rx中断服务
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void)
{
while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready?
Receive_Data=UCA0RXBUF;
IFG2 &= ~UCA0RXIFG;
__bic_SR_register_on_exit(LPM0_bits);
}
可以看到在接收中断中使单片机退出低功耗模式,为什么这么做?
当我们在使用scanf
函数的时候我们是不是要等待输入呢?这个等待的操作在上面的基本函数get_char
中实现:
/*
*接收一个字符
*/
uchar get_char(void)
{
__bis_SR_register(LPM0_bits); //进入低功耗等待接受中断发生
return Receive_Data; //返回接收中断返回的数据
}
- 当程序中使用
scanf
函数会调用get_char
这时便会进入低功耗模式等待接收中断的发生。 - 若收到数据,中断发生,此时在中断中将接收到的数据传送给全局变量
Receive_Data
紧接着退出低功耗模式。 - 此时程序回到
get_char
处继续执行return Receive_Data;
便实现了返回接收字符的功能。
第三步,移植
对于putchar函数的重定向很简单
/*
*putchar 重定向
*/
int putchar(int ch)
{
if(ch=='\n')
Send_Char('\r'); //回车前添加换行符
Send_Char(ch);
return ch;
}
而 getchar
正常基本功能来讲做到下面就可以了
int getchar(void)
{
return get_char();
}
但是我们想要在超级终端上正常的进行交互,那输入的时候我们想要看到自己输入的字符怎么办?万一输入错一个字符想要删除怎么办?scanf函数本身并不支持删除操作,他只能顺序解析ASCII码字符串,所以为了实现退格操作需要开辟一个缓冲区,先处理键盘的输入与退格操作,等到输入完毕按回车键时,再将缓冲区的内容依次送回。
/*
*getchar 重定向
*/
int getchar(void) {
static char io_buffer[LINE_LENGTH + 2]; //Where to put chars
static char ptr; //Pointer in buffer
char c;
while (1) {
if (io_buffer[ptr]) //如果缓冲区有字符
return (io_buffer[ptr++]); //则逐个返回字符
ptr = 0; //直到发送完毕,缓冲区指针归零
while (1) //缓冲区没有字符,则等待字符输入
{
c = get_char(); //等待接收一个字符
if (c == In_EOF && !ptr) //==EOF== Ctrl+Z
{ //只有在未入其他字符时才有效
put_message(Out_EOF); //终端显示EOF符
return EOF; //返回 EOF(-1)
}
if (c == In_DELETE || c == In_BACKSP) //==退格或删除键==
{
if (ptr) //缓冲区有值
{
ptr--; //从缓冲区移除一个字符
put_message(Out_DELETE); //同时显示也删掉一个字符
}
} else if (c == In_SKIP) //==取消键 Ctrl+C ==
{
put_message(Out_SKIP); //终端显示跳至下一行
ptr = LINE_LENGTH + 1; //==0 结束符==
break;
} else if (c == In_EOL) //== '\r' 回车==
{
putchar(io_buffer[ptr++] = '\n'); //终端换行
io_buffer[ptr] = 0; //末尾添加结束符(NULL)
ptr = 0; //指针清空
break;
} else if (ptr < LINE_LENGTH) //== 正常字符 ==
{
if (c >=0x20) //删除 0x20以下字符
{
//存入缓冲区
putchar(io_buffer[ptr++] = c);
}
} else //缓冲区已满
{
putchar('\7'); //== 0x07 蜂鸣符,PC回响一声
}
}
}
}
下面是常用特殊ASCII码的宏定义
//--------------------------------------------------------------------------------
#define LINE_LENGTH 20 //行缓冲区大小,决定每行最多输入的字符数
/*标准终端设备中,特殊ASCII码定义,请勿修改*/
#define In_BACKSP 0x08 //ASCII <-- (退格键)
#define In_DELETE 0x7F //ASCII <DEL> (DEL 键)
#define In_EOL '\r' //ASCII <CR> (回车键)
#define In_SKIP '\3' //ASCII control-C
#define In_EOF '\x1A' //ASCII control-Z
#define Out_DELETE "\x8 \x8" //VT100 backspace and clear
#define Out_SKIP "^C\n" //^C and new line
#define Out_EOF "^Z" //^Z and return EOF
//---------------------------------------------------------------------------------
第四步,配置IAR环境
至此重定向工作基本算是完成了,只需要在工程中包含stdio.h即可。
当然IAR要做一些设置:
Project–>Options–>General Options–>Library Configuration.将Library设置为CLIB
第五步,配置超级终端
新建连接时,端口设置选项的数据流控制选“无”。
其它相关设置:
完成并测试
通过以下测试代码:
#include "includes.h"
int a;
void main( void )
{
Sys_Init();
Uart_Init();
while(1)
{
printf("give a num!\n");
scanf("%d",&a);
printf("the num is %d\n",a);
}
}
测试结果如下
以上。