0 写在前面
以下内容针对“S3C2440”这款ARM9芯片。虽然不同硬件的操作(寄存器)不同,但是UART原理是相通的。
因此,把重点放在UART的原理及如何理清硬件操作流程!换句话说,重在硬件操作
流程思维的培养,即演绎能力。即使后面讲到I2C、SPI等协议编程时,掌握这种演绎能力至关重要。
下面主要讲解UART的原理以及通过UART通信协议实现一个最基本的数据收发操作
1 UART的原理
说起串口,大家比较耳熟能详。而UART:Universal Asynchronous Receiver/Transmitter 通用异步收发器。
对于UART理解,抓住两个词:异步 收发
异步:表明通信双方可任意时刻收发数据
收发:表明彼此具有同时收发功能,而不受各自干扰。
再来看看硬件连接图:
很简单,对不对?只需三根线,发送对应接收即可
这也是UART广泛使用的原因:简单、稳定
接着,通信双方如何收发数据呢?即UART的通信协议是怎样的?
回想串口通信前的参数设置:波特率、开始位、停止位、校验位、数据位等,这些跟UART通信协议息息相关。
即理解了它们,UART的通信就不难理解了。
波特率(Baud rate):约定彼此收发数据的频率,即每1bit所占时间的约定
开始位(Start bits):数据位传输前标识位,表明后面的是数据位
数据位(Data bits):指明发送/接收数据的位数
校验位(Parity):判断接收的数据是否正确,这在以前常使用,而现在基本不用检验位了(因为现在通信更稳定了,数据出错率很低)
停止位(Stop bits):数据位结束的标志,也是为下一次传输做好准备
如下图UART通信参数设置:(流控一般不用,默认关闭)
补充一点:串口数据收发是串行的,即位流方式
OK,有了上面一些概念的理解,来看看收发时序图,捋一捋整个通信过程
补充一点:检验位:奇/偶校验。即数据位 + 检验位 中 1 的个数位奇数/偶数 。只具备接收数据检测,不具备矫正
分析一下:
发送:开始位 --> data[0] -->data[1] --> data[2].......-->data[7] --->[校验位] --> 停止位
接收:在每个bit中间采样并量化读取,即接收顺序为:data[0] --> data[1] --> ....-->data[7]
拓展一点:逻辑电平1/0的划分是相对的,即高低电平根据实际采样电压的范围量化为1或0
如TTL/CMOS门电路和RS232的高低电平划分相反,如下图
2 UART通信编程
接着,有了以上对UART原理的基本理解,那如何编程实现通信呢?
这里以S3C2440这款ARM9芯片为例,编写UART通信代码实现基本的数据收发。
提醒一点:重在理清UART编程时的操作流程,即掌握了套路,换成其它芯片也适用。
1)明确硬件收发流程
对于S3C2440这款芯片,UART方块图如下
2)理清编程步骤与项目组成
uart.c 函数内容
1>串口初始化函数: void uart0_init(void)
a) 设置引脚用于串口
b) 设置波特率
c) 设置数据格式(数据位、校验位等)
2>发送数据函数 :int putchar(char c)
3>接收数据函数 :int getchar(void)
uart.h
上面三个函数的声明
main.c : 程序主入口,进行串口初始化后,接收终端输入并回显
start.S: 设置堆栈后调用main函数
Makefile:管理项目的构建(编译+链接)
3)写代码
uart.c 文件内容如下:
/* uart0_init: 串口初始化*/
void uart0_init()
{
/* 1.设置引脚用于串口 */
/* GPH2,3 用于TxD0, RxD0*/
GPHCON &= ~((3<<4) | (3<<6)); // 清空GPHCON中GPH2,3对应位
GPHCON |= ((2<<4) | (2<<6)); // 设置GPH2,3分别为TxD0, RxD0
GPHUP &= ~((1<<2) | (1<<3)); // 默认拉高电平
/* 2.设置波特率:115200 */
/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
* UART CLOCK = 50 MHz
* UBRDIVn = (int)(50000000 / (115200 x 16 )) -1 = 26
*/
UCON0 = 0x00000005; /* 选择时钟源PCLK,中断/查询模式 */
UBRDIV0 = 26;
/* 3.设置数据格式 */
ULCON0 = 0x00000003; /* 8n1: 8个数据位,无校验位,1个停止位*/
}
/* putchar: 发送一个字节数据 */
int putchar(int c)
{
while(!(UTRSTAT0 & (1<<2))); // 等待发送移位器为空
UTXH0 = (unsigned char)c;
}
/* getchar: 接收一个字节数据 */
int getchar(void)
{
while(!(UTRSTAT0 & (1<<0))); // 等待接收移位器满
return URXH0;
}
/* puts: 发送一个字符串 */
int puts(const char* s)
{
while(*s)
{
putchat(*s);
s++;
}
}
uart.h文件
#ifndef _UART_H
#define _UART_H
void uart0_init();
int putchar(int c);
int getchar(void);
int puts(const char*s);
#endif
main.c文件:
#include "s3c2440_soc.h"
#include "uart.h"
int main(void)
{
unsigned char c;
uart0_init();
puts("Hello, world!\n\r");
while(1)
{
c = getchar();
if(c == '\r')
putchar('\n');
if(c == '\n')
putchar('\r');
putchar();
}
return 0;
}
Makefile文件
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Ttext 0 start.o led.o uart.o main.o -o uart.elf
arm-linux-objcopy -O binary -S uart.elf uart.bin
arm-linux-objdump -D uart.elf > uart.dis
clean:
rm *.bin *.o *.elf *.dis
start.S s3c2440_soc.h led.o 文件不是重点,略去
4)测试结果
OK,运行结果没问题
总结一下:
UART的通信实现相对简单。看似只是操作几个寄存器,但要重点理解UART硬件收发数据的流程。
UART的通信原理不难理解。此外,在代码编写前,一定要养成事先理清操作步骤及项目文件组织。
简言之,要有意识锻炼编程思维,养成良好编程习惯。
参考教材:
韦东山老师的新一期视频《UART硬件介绍 + UART编程 》两节内容