再上一篇:第20章
上一篇:20.2 uVison 使用入门
主页
下一篇:20.4 测试示例程序
再下一篇:20.5 使用调试器
文章列表

20.3 使用 UART 输出“Hello World”

《Cortex-M3 权威指南》,嵌入式处理器开发教程。

在上一个例子中,我们使用了C标准库中的printf函数。但是C标准库并不知道我们使用的硬件 是什么,因此如果要“真实”地输出字符串(如通过UART输出),还必须添加一些代码。正如本书 曾经提到的,为使输出送到实际的硬件,我们经常需要做所谓的“retargeting”工作。与

Retargeting相关的函数除了可以用于输出文本外,还可以包含处理错误以及终结程序等其它功能。 在本例,只介绍文本输出的功能。

在本例中,是打算把”Hello World”消息从LM3S811的UART0送出去,目标系统是Luminary MicroLM3S811评估板。板上晶振为6MHz,但单片机片内配有PLL模块,它把时钟频率上升到50MHz。 波特率是115,200,并且使用PC上的超级终端程序来接收从UART发出的数据。
要对printf执行retarget处理,我们需要实现fputc函数。在下面的代码中,我们就创建了这 个fputc函数,它又呼叫sendchar函数,而后者则操作UART输出字符:

#include "stdio.h"

#define CR 0x0D //回车符

#define LF 0x0A //换行符

void Uart0Init(void); void SetClockFreq(void); int sendchar(int ch);

// 若使用6MHz,则注释掉下一行

#define CLOCK50MHZ

// Register addresses

#define SYSCTRL_RCC ((volatile unsigned long *)(0x400FE060))

#define SYSCTRL_RIS ((volatile unsigned long *)(0x400FE050))

#define SYSCTRL_RCGC1 ((volatile unsigned long *)(0x400FE104))

#define SYSCTRL_RCGC2 ((volatile unsigned long *)(0x400FE108))

#define GPIOPA_AFSEL ((volatile unsigned long *)(0x40004420))

#define UART0_DATA ((volatile unsigned long *)(0x4000C000))

#define UART0_FLAG ((volatile unsigned long *)(0x4000C018))

#define UART0_IBRD ((volatile unsigned long *)(0x4000C024))

#define UART0_FBRD ((volatile unsigned long *)(0x4000C028))

#define UART0_LCRH ((volatile unsigned long *)(0x4000C02C))

#define UART0_CTRL ((volatile unsigned long *)(0x4000C030))

#define UART0_RIS ((volatile unsigned long *)(0x4000C03C))

int main (void)

{

SetClockFreq(); // 建立时钟的配置 (50MHz/6MHz) Uart0Init(); // 初始化UART0

printf ("Hello world!\n");

while (1);

}

void SetClockFreq(void)

{

#ifdef CLOCK50MHZ

// 置位BYPASS, 清除 USRSYSDIV 和 SYSDIV

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xF83FFFFF) | 0x800 ;

// 清零 OSCSRC, PWRDN 和 OEN

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xFFFFCFCF);

//修改 SYSDIV, 设置 USRSYSDIV 和 Crystal 位段的值

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xF87FFC3F) | 0x01C002C0;

// 等待PLLLRIS被置位

while ((*SYSCTRL_RIS & 0x40)==0); // wait until PLLLRIS is set

// 清除bypass

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xFFFFF7FF) ;

#else

// 置位 BYPASS, 清除 USRSYSDIV 和 SYSDIV

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xF83FFFFF) | 0x800 ;

#endif return;

}

void Uart0Init(void)

{

*SYSCTRL_RCGC1 = *SYSCTRL_RCGC1 | 0x0003; // 使能 UART0 & UART1

// clock

*SYSCTRL_RCGC2 = *SYSCTRL_RCGC2 | 0x0001; // 使能 PORTA 时钟

*UART0_CTRL = 0; // 除能 UART

# ifdef CLOCK50MHZ

*UART0_IBRD = 27; // 以50MHz频率为基准编程波特率

*UART0_FBRD = 9;

# else

*UART0_IBRD = 3; // 以6MHz频率为基准编程波特率

*UART0_FBRD = 17;

# endif

*UART0_LCRH = 0x60; // 8 bit, 无奇偶

*UART0_CTRL = 0x301; // 使能 TX 和 RX, 以及 UART 使能

*GPIOPA_AFSEL = *GPIOPA_AFSEL | 0x3; // 把GPIO管脚交给UART0控制

return;

}

// 送给UART0一个字符(printf函数使用它来输出文字)

int sendchar (int ch)

{

if (ch == '\n')

{

while ((*UART0_FLAG & 0x8)); // 如果UART忙碌中则等待

*UART0_DATA = CR; // 输入附加的CR以使字符串被正确显示

}

while ((*UART0_FLAG & 0x8)); // 如果UART忙碌中则等待

return (*UART0_DATA = ch); // 输出数据

}

//文本输出的retargeting代码

int fputc(int ch, FILE *f)

{

return (sendchar(ch));

}

代码中的SetupClockFreq()用于把系统时钟设置为50MHz。要注意的是这个函数是与具体的
器件相关的。另外,还使用了条件编译来允许选择使用6MHz的原始频率。

UART的初始化是由Uart0Init()来执行的,它设置了波特率为115200,8个数据位,1个停止 位,无奇偶校验,并且启用GPIO的第二功能,从而让GPIO控制器把管脚的控制权交给UART0。在使 用UART和GPIO之前,必须先启用这两个模块。代码中的把SYSCTRL_RCGC1和SYSCTRL_RCGC2分别 启用了这两个模块。

Retargeting的代码是由粗体的fputc()执行的。要注意的是,不能使用其它的名字,因 为”fputc”是编译器预定义的用于字符输出的函数名。fputc()实际上只是个封皮,它直接调用 sendchar()来做真实的工作。sendchar()除了输出一般的字符之外,还要在检测到”\n”时输出一 个附加的CR,才能在超级中断上正常显示回车换行,否则将回到同一行的起始处,使先前输出的字 符被新输出的字符覆盖掉。

在加入retargeting代码后,就可以重新编译了。