全部代码如下,先看一下整体!
//头文件
#include "hal.h"
#include "stdio.h"
// 函数声明
UINT8 UART0_Init();
void main(){
//使用外部时钟
SET_MAIN_CLOCK_SOURCE(CRYSTAL);
//串口初始化
UART0_Init();
//字符输出 输出1
putchar('1');
//字符串输出 输出Hello CC2430
printf("Hello CC2430!\n");
while(1){
}
}
int putchar(int c){
if(c== '\n'){
while(!UTX0IF);
UTX0IF = 0;
U0DBUF = '\r';
}
while(!UTX0IF);
UTX0IF = 0;
U0DBUF = c;
return c;
}
UINT8 UART0_Init(){
//UART0 IO口定位
IO_PER_LOC_UART0_AT_PORT0_PIN2345();
//9600 8 N 1
UART_SETUP(0,9600,HIGH_STOP);
UTX0IF = 1;
return 0;
}
下面一步一步分析一下
1头文件
#include "hal.h" 该头文件包括了操作CC2430常用寄存器的宏,书中找到的觉得很实用。
#include "stdio.h" 调用printf函数,重定义putchar函数
2 定义系统时钟 SET_MAIN_CLOCK_SOURCE(CRYSTAL);
设定了CC2430的时钟频率,即使用片外32MHz。根据数据手册,CC2430可以有两种时钟,第一种是片外的32M石英晶振,另一种是片内的16M的RC振荡器。顺便说一下,这个SET_MAIN_CLOCK_SOURCE() 是一个名为hal.h文件中的“动作宏”。我手头有一本书,名为《ZigBee技术实践教程》,其中的代码都使用了这个头文件。该头文件中有很多的API函数,若仔细的查看代码,配合数据手册页可以看懂其中各宏的“原理”。设定时钟的的函数原型是这样的:
#define SET_MAIN_CLOCK_SOURCE(source) \
do { \
if(source) { \
CLKCON |= 0x40; \
while(!HIGH_FREQUENCY_RC_OSC_STABLE); \
if(TICKSPD == 0){ \
CLKCON |= 0x08; \
} \
SLEEP |= 0x04; \
} \
else { \
SLEEP &= ~0x04; \
while(!XOSC_STABLE); \
asm("NOP"); \
CLKCON &= ~0x47; \
SLEEP |= 0x04; \
} \
}while (0)
对应着数据手册,完全可以看懂其中的“玄机”。由于我还处在学习CC2430的初级阶段,以后就根据这个头文件编写程序。至于寄存器的话,大概知道其中的作用即可
领悟到的小技巧:
1 如果需要使用一个复杂的动作宏的话,多行书写使用 \ 。
2 所有的这些动作都包含在 do{} while(0)中。
3 定义IO口IO_PER_LOC_UART0_AT_PORT0_PIN2345();
这句话把UART0的IO口定义到P02 P03 P04 P05。函数原型是这样的:
#define IO_PER_LOC_UART0_AT_PORT0_PIN2345() do { PERCFG = (PERCFG&~0x01)|0x00; } while (0)
4 设定串口相关参数UART_SETUP(0,9600,HIGH_STOP);
函数原型是这样的:
#define UART_SETUP(uart, baudRate, options) \
do { \
if((uart) == 0){ \
if(PERCFG & 0x01){ \
P1SEL |= 0x30; \
} else { \
P0SEL |= 0x0C; \
} \
} \
else { \
if(PERCFG & 0x02){ \
P1SEL |= 0xC0; \
} else { \
P0SEL |= 0x30; \
} \
} \
\
U##uart##GCR = BAUD_E((baudRate),CLKSPD); \
U##uart##BAUD = BAUD_M(baudRate); \
\
U##uart##CSR |= 0x80; \
\
\
U##uart##UCR |= ((options) | 0x80); \
\
if((options) & TRANSFER_MSB_FIRST){ \
U##uart##GCR |= 0x20; \
} \
} while(0)
5UTX0IF = 1; UART0发送完成标志位1
若串口数据发送完成,该寄存器被置位。若寄存器被置位,需要通过软件清零。这个也是很好理解的。
6putchar和printf函数
说完了初始化,再来谈谈如何使用putchar和printf函数。Putchar和printf都是C语言标准库函数。Printf调用putchar实现字符数据的传送。翻看IAR的代码,获得了使用putchar函数的信息。IAR中来了一段51单片机的代码。该代码位于EW8051_CompilerReference文件中。其中有这么一段参考代码。
#include <io8052.h>
int putchar(int c) {
if (c == '\n') {
while (!SCON_bit.TI);
SCON_bit.TI = 0;
SBUF = 0x0d;
}
while (!SCON_bit.TI);
SCON_bit.TI = 0;
return (SBUF = c);
}
这段代码有点51基础的都可以看懂。唯一需要说明的就是,如果出现转义字符\n(回车)的话,先输出转义字符\t(换行)。简单说,windows喜欢先来换行(\r),再来回车(\n)。而linux中只要一个回车就OK。
同理,CC2430的代码就是可以这样写:
int putchar(int c){
if(c== '\n'){
while(!UTX0IF);
UTX0IF = 0;
U0DBUF = '\r';
}
while(!UTX0IF);
UTX0IF = 0;
U0DBUF = c;
return c;
}
7 IAR中printf参数设置
接下来必须说说IAR中相关的设置,在EW8051_UserGuide中,指出了printf和scanf函数的相关设置。printf包含了small medium large三个选项。每个选型包含不同的printf功能,当然功能越全,消耗的资源也就越多。在这里我就先设置成small了。IAR的说明文件中也给出了这个选项参数所提供的功能,如下表所示。
8实验结果
最后给出实验的结果,虽然代码非常的简单,但是我还是花了一整个下午去调试,后来发现其实是IAR中的link中某参数选择错误了。实验的结果还是预想的那样,使用的时候还是要注意串口的参数设定,CC2430的串口调试助手的参数要一致。
总结
串口的使用非常的重要,它是PC机和单片机的重要桥梁。现在,单片机负责采集数据,而PC机负责分析处理数据。在这个示例代码中,通过使用hal头文件,避免了复杂难记的寄存器操作,从而快速的初始化串口;通过改写putchar函数,写出了适合CC2430串口输出的函数;最后使用了标准的printf函数实现了串口输出。