• 嵌入式Linux入门-彻底理解UART串口,手把手教你写程序


    UART串口这个东西,是嵌入式学习上避不开的,不仅在调试中经常用到,还有很多模块通过串口与SOC相连。这篇文章让你彻彻底底,搞明白串口程序的编写。

    没有基础的先看:

    嵌入式Linux学习系列全部文章:嵌入式Linux学习—从裸机到应用教程大全 

    目录

    1. UART串口

    1.1 UART硬件连接

    1.2 UART软件通信协议

    2. 读手册,编程序

    2.1 找对应引脚

    2.2 设置GPIO为UART功能

    2.3 设置UART(初始化)

    2.4 编写发送接收函数

    3. 完整代码和验证


    1. UART串口

    全称:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,简称UART)是一种串行异步收发协议。

    用的最多的地方就是开发板的串口连接电脑发送信息了,我们先看看电脑端什么样的:

    1.1 UART硬件连接

    UART硬件连接比较简单,仅需要3条线,如下图所示:

    TX:发送数据端,要接对面设备的RX
    RX:接收数据端,要接对面设备的TX
    GND:保证两设备共地,有统一的参考平面

    和电脑连接用这个东西:

    usb转换板

     这个东西可以把RS232电平转换为TTL电平,为什么要转换电平,这里不赘述,随便搜一下就知道了。如果你的开发板集成了电平转换就不需要这个东西,直接用USB线连接到电脑。

    1.2 UART软件通信协议

    首先我们要知道UART协议中数据是一位一位(0或1)发送的,并且连续的一串数据被分成了一帧一帧发送的,下图便是一帧数据(不包含空闲位)。

    在这里插入图片描述

    uart传输数据的顺序就是:刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。接下来接着像上述过程一直传送。

    协议如下:
    空闲位:
    UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平
    起始位:
    开始进行数据传输时,发送方先发出一个低电平’0’,表示传输字符的开始。
    数据位:
    起始位之后就是要传输的数据,数据可以是5,6,7,8,9位,构成一个字符,一般都是8位。

    传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输。比如传输“A”如果是MSB那么就是01000001,如果是LSB那么就是10000010
    奇偶校验位:
    数据位传送完成后,要进行奇偶校验,校验位其实是调整个数,串口校验分几种方式:
    1.无校验(no parity)
    2.奇校验(odd parity):如果数据位中’1’的数目是偶数,则校验位为’1’,如果’1’的数目是奇数,校验位为’0’。
    3.偶校验(even parity):如果数据为中’1’的数目是偶数,则校验位为’0’,如果为奇数,校验位为’1’。
    4.mark parity:校验位始终为1
    5.space parity:校验位始终为0

    以传输“A”(01000001)为例:
    1、当为奇数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为1才能满足1的个数为奇数(奇校验)。
    2、当为偶数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为0才能满足1的个数为偶数(偶校验)。
    通过配置相应寄存器,此位可以去除,即不需要奇偶校验位。通常是不需要的。
    停止位:
    数据结束标志,可以是1位,1.5位,2位的高电平。
    波特率:
    数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率9600bps,115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。

    例如:串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。

    再例如:数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200位/秒=1200波特。

    2. 读手册,编程序

    2.1 找对应引脚

    手册告诉我们S3C2440有三个UART,那么哪个能用呢?我们找开发板上那个转了USB的方便与电脑连接。你手上的可能不一样,随便用一个就行。

    翻一翻开发板的原理图

    找到了,我的开发板有串口转USB功能

    接着看,RxD0和TxD0连到了S3C2440的哪个引脚,

    搜索一下,找到了,GPH2和GPH3,我们就用他了。 

    2.2 设置GPIO为UART功能

    翻开S3C2440的数据手册,找到IO那一章。

     找到GPIOH的控制寄存器地址:0x56000070.

     GPH3配置为TXD0M,就是把GPIOH第6、7为分别置为1和0,GPH2同理。

    代码就出来了

    1. /* 设置引脚用于串口 */
    2. /* GPH2,3用于TxD0, RxD0 */
    3. volatile unsigned int *GPHCON=0x56000070;
    4. *GPHCON &= ~((3<<4) | (3<<6));
    5. *GPHCON |= ((2<<4) | (2<<6));

    不懂volatile和位运算的可以看这篇:嵌入式C语言重点(const、static、voliatile、位运算)

    别忘了,前面说过:

    空闲位:
    UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平。

    因此还得把端口内部上拉电阻设置一下,让他在空闲时,输出高电平

    找到寄存器GPHUP的地址:0x56000078.

     把寄存器GPHUP第2、3位设置为0就行。

    1. volatile unsigned int *GPHUP=0x56000078;
    2. *GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

    2.3 设置UART(初始化)

    根据第一部分内容,我们知道,要设置帧格式:校验位、停止位、数据长度、波特率

    目标:校验位:无,停止位1,数据长度:8,波特率:115200

    首先找到控制帧格式的寄存器:

    ULCON0地址为0x50000000。

    校验位:

    校验位设置如上图,我们不需要校验位,刚好默认就是没有,不用设置了。

    停止位:

    停止位设置如上图,我们设置为1位停止位,刚好默认值也是1位,又不用设置了。

    数据位:

    我们想设置为8位长度。

    这次不能用默认了,得把1、0位设置为1、1.

    1. volatile unsigned int *ULCON0=0x50000000;
    2. /* 设置数据格式 */
    3. *ULCON0 = 0x00000003; /*8个数据位 */

    波特率

    UART clock可以用PCLK、FCLK\n、UEXTCLK,我们就用PCLK

    我们想让波特率buad rate=115200

    根据上面公式,计算一下

    UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
    UART clock = 50M
    UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26

    上图又表明UBRDIV0地址为0x50000028,代码就出来了。

    1. volatile unsigned int *UBRDIV0=0x50000028;
    2. *UBRDIV0 = 26;

    UART模式

    还得设置一下控制器,选择传送模式,UART支持DMA,但是我们不用。

    包括上面提到的

    UART clock可以用PCLK、FCLK\n、UEXTCLK,我们用PCLK也得设置一下

    这两个设置都在UART控制寄存器。

     找到UCON0地址0x50000004.

     默认UART clock就是用PCLK,不用管了。

     我们用这个中断或轮询模式。

    1. volatile unsigned int *UCON0=0x50000004
    2. *UCON0 = 0x00000005; /* PCLK,中断/查询模式 */

    综合上述,得到UART初始化代码

    1. volatile unsigned int *GPHCON=0x56000070;
    2. volatile unsigned int *GPHUP=0x56000078;
    3. volatile unsigned int *ULCON0=0x50000000;
    4. volatile unsigned int *UBRDIV0=0x50000028;
    5. volatile unsigned int *UCON0=0x50000004;
    6. /* 设置引脚用于串口 */
    7. /* GPH2,3用于TxD0, RxD0 */
    8. *GPHCON &= ~((3<<4) | (3<<6));
    9. *GPHCON |= ((2<<4) | (2<<6));
    10. *GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */
    11. /* 设置数据格式 */
    12. *ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */
    13. /* 设置波特率 */
    14. /* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
    15. * UART clock = 50M
    16. * UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
    17. */
    18. *UBRDIV0 = 26;
    19. /* PCLK,中断/查询模式 */
    20. *UCON0 = 0x00000005;

    2.4 编写发送接收函数

    UART发送和接收分别有寄存器来保存数据,同时又有相应的状态寄存器。可以读取状态寄存器的值来判断发送或者接收完数据没有。

    这里就直接给出简单的发送接收代码,大家可以自己去芯片手册找到寄存器,要多读手册,才能提高水平。

    1. int putchar(int c)
    2. {
    3. /* UTRSTAT0 */
    4. /* UTXH0 */
    5. while (!(UTRSTAT0 & (1<<2)));
    6. UTXH0 = (unsigned char)c;
    7. }
    8. int getchar(void)
    9. {
    10. while (!(UTRSTAT0 & (1<<0)));
    11. return URXH0;
    12. }
    13. int puts(const char *s)
    14. {
    15. while (*s)
    16. {
    17. putchar(*s);
    18. s++;
    19. }
    20. }

    3. 完整代码和验证

    启动代码和makefile先给出,不知道怎么来的,先看一下我之前的两篇文章:

    1.嵌入式Linux入门-从启动代码开始,真正从0开始点个灯

    2.嵌入式Linux入门-读数据手册,设置时钟,让代码跑得更快

    启动代码:

    1. .text
    2. .global _start
    3. _start:
    4. /* 关闭看门狗 */
    5. ldr r0, =0x53000000
    6. ldr r1, =0
    7. str r1, [r0]
    8. /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    9. /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    10. ldr r0, =0x4C000000
    11. ldr r1, =0xFFFFFFFF
    12. str r1, [r0]
    13. /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
    14. ldr r0, =0x4C000014
    15. ldr r1, =0x5
    16. str r1, [r0]
    17. /* 设置CPU工作于异步模式 */
    18. mrc p15,0,r0,c1,c0,0
    19. orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
    20. mcr p15,0,r0,c1,c0,0
    21. /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
    22. * m = MDIV+8 = 92+8=100
    23. * p = PDIV+2 = 1+2 = 3
    24. * s = SDIV = 1
    25. * FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
    26. */
    27. ldr r0, =0x4C000004
    28. ldr r1, =(92<<12)|(1<<4)|(1<<0)
    29. str r1, [r0]
    30. /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
    31. * 然后CPU工作于新的频率FCLK
    32. */
    33. /* 设置内存: sp 栈 */
    34. ldr sp, =4096 /* nand启动 */
    35. bl main
    36. halt:
    37. b halt

    Makefile:

    1. all:
    2. arm-linux-gcc -c -o uart.o uart.c
    3. arm-linux-gcc -c -o start.o start.S
    4. arm-linux-ld -Ttext 0 start.o uart.o -o uart.elf
    5. arm-linux-objcopy -O binary -S uart.elf uart.bin
    6. clean:
    7. rm *.bin *.o *.elf

    c代码:

    在main函数中向电脑发个“Hello World”,并且回送电脑发过来的数据

    1. #include
    2. int putchar(int c)
    3. {
    4. /* UTRSTAT0 */
    5. volatile unsigned int *UTRSTAT0=0x50000010;
    6. volatile unsigned int *UTXH0=0x50000020;
    7. /* UTXH0 */
    8. while (!(*UTRSTAT0 & (1<<2)));
    9. *UTXH0 = (unsigned char)c;
    10. }
    11. int getchar(void)
    12. {
    13. volatile unsigned int *UTRSTAT0=0x50000010;
    14. volatile unsigned int *URXH0=0x50000024;
    15. while (!(*UTRSTAT0 & (1<<0)));
    16. return *URXH0;
    17. }
    18. int puts(const char *s)
    19. {
    20. while (*s)
    21. {
    22. putchar(*s);
    23. s++;
    24. }
    25. }
    26. int uart0_init(void)
    27. {
    28. volatile unsigned int *GPHCON=0x56000070;
    29. volatile unsigned int *GPHUP=0x56000078;
    30. volatile unsigned int *ULCON0=0x50000000;
    31. volatile unsigned int *UBRDIV0=0x50000028;
    32. volatile unsigned int *UCON0=0x50000004;
    33. /* 设置引脚用于串口 */
    34. /* GPH2,3用于TxD0, RxD0 */
    35. *GPHCON &= ~((3<<4) | (3<<6));
    36. *GPHCON |= ((2<<4) | (2<<6));
    37. *GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */
    38. /* 设置数据格式 */
    39. *ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */
    40. /* 设置波特率 */
    41. /* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
    42. * UART clock = 50M
    43. * UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
    44. */
    45. *UBRDIV0 = 26;
    46. /* PCLK,中断/查询模式 */
    47. *UCON0 = 0x00000005;
    48. }
    49. int main(void)
    50. {
    51. unsigned char c;
    52. uart0_init();
    53. puts("Hello, world!\n\r");
    54. while(1)
    55. {
    56. c = getchar();
    57. if (c == '\r')
    58. {
    59. putchar('\n');
    60. }
    61. if (c == '\n')
    62. {
    63. putchar('\r');
    64. }
    65. putchar(c);
    66. }
    67. return 0;
    68. }

    make命令,得到二进制文件,烧写,结果:

     Hello,world出现了,随便输入也能回显,完美。

  • 相关阅读:
    黑马Java热门面试题Monngo&ES(十)
    《QT+PCL第六章》点云配准icp系列6
    [Java]快速入门优先队列(堆),手撕相关面试题
    bitmap实践-留存计算
    Java环境变量配置详细教程
    C++雾中风景18:C++20, 从concept开始
    MySQL进阶-事务及索引
    企业如何落地搭建商业智能BI系统
    Spring、MyBatis框架和Redis数据库介绍 第3关:Redis数据库简介
    mysql数据库 操作常用命令
  • 原文地址:https://blog.csdn.net/freestep96/article/details/126491784