在本系列的第四篇文章中,讲述了串口发送数据基本配置方法;而串口除了发送打印数据外,还可以接收由外部发送的数据,实现外界与单片机的数据交互,且这种数据交互方式比iic与spi等都要简单,自由;因此,能用串口接收数据也是单片机功能学习中必须要掌握的一部分。
串口接收数据主要通过中断来实现,中断的概念在本系列的第三篇文章中提到过,这里再重复一下:中断有很多的触发方式,你在代码中先要设定好它的触发方式,待到单片机在运行过程中能满足这个条件的话,就会进入你已经设定好的对应中断的中断服务函数。(笔者语言表达能力实在有限,如果还听不懂的话就看各位微机老师的努力了。)
一、核心语句
1.
GPIOPinConfigure(GPIO_PA0_U0RX);
GPIOPinConfigure(GPIO_PA1_U0TX);
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
这三条语句是对TM4的io口做其他功能时也要复用,
这里**GPIOPinConfigure() ,GPIOPinTypeUART()**这两句语句的配合才能完成TM4IO口功能的复用。
2.
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
UARTStdioConfig(0, 115200, SysCtlClockGet());
这两条语句都可以用来配置一个串口,但不能同时拿来配置同一个串口,只能使用其中的一个,相关区别如下:
对于
UARTStdioConfig(0, 115200, SysCtlClockGet());
这条语句所配置的串口,可以使用UARTprintf("Turn on the LED1\n");
这样的函数,但我们注意到,使用UARTprintf并没有指定是哪个串口使用,这里对应的就是上面函数中第一个参数0,即串口0,该取值可以是0-2,代表着串口0-2用这条语句配置的话就能使用UARTprintf函数,注意这个只能在同一时间用来配置一个串口,且用这个函数配置的串口不能使用UARTCharPut(UART0_BASE,'k');
这个函数。
对于
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
这条语句的用法与上一个正好相反,这条语句配置的串口只能使用UARTCharPut而不能使用UARTprintf函数,这个函数的配置可以是所有串口,第一个是你串口的基地址,第三个是波特率,其他就默认就好。与UARTStdioConfig函数相比,本条语句更适合在具体功能上使用,而UARTStdioConfig更适合用于调试时打印数据使用。
说到这里在啰嗦几句:一般在用单片机做工程时,与单片机usb口直接相连的那个串口我们一般不用做具体功能而只是单纯打印数据来作辅助使用,一般使用其他的串口接入具体功能!(因为与USB相连的这个串口总是会出现奇奇怪怪的问题)
3.
中断配置:
中断的配置基本可以分为四步:
1.配置相应的中断触发方式并配置优先级(这一步的代码每个功能都不一样)
2.中断注册
3.中断使能
4.编写中断服务函数
3.1配置相应的中断触发方式并配置优先级
在串口中断中,需要先引入一个FIFO的概念,指缓存区,我们要设置的是串口接收中断,串口接收到的字符不是第一时间就会触发中断,而是会存在缓存区中,待到缓存区满了才会触发一次中断;这个FIFO一共有最大为16个字符的缓存区,而如果你代码中没有配置FIFO的话默认是开1/2的深度(它会默认使能),也就是八个字符的缓存区,我们这里开启1/4也就是四个字符的缓存区,具体代码为
UARTFIFOEnable(UART0_BASE);//先使能
UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX2_8, UART_FIFO_RX2_8);//在打开2/8也就是1/4的深度
ps:如果直接把FIFO失能(disable)的话,就可以一个字符触发一次中断。
接着,对于串口接收中断,配置语句为
UARTIntEnable(UART0_BASE, UART_INT_RX);
这里指串口0配置为接收中断(RX指接收);
IntPrioritySet(INT_UART0, 0x0);
这一句的意思是配置中断优先级,优先级为0xE0-0x0的值,0x0的优先级最高;
3.2中断注册
中断注册再来详细讲一下,中断注册的作用可以理解为你现在已经设定好了中断触发方式,等到中断满足触发条件触发了,他要跳去一个中断服务函数,那这个中断服务函数是谁呢,由谁来决定呢?这个主要就是靠中断注册函数来决定!
UARTIntRegister(UART0_BASE, UART0IntHandler);
具体函数只有这一句,第一个参数为中断源,第二个参数是你想要前往的中断服务函数的名称(这里中断服务函数都是自己编写的,不是系统自动生成的),这里需要注意一点:中断服务函数必须放在中断注册函数的前面,先声明也可以,不然会报错。
3.3中断使能
IntEnable(INT_UART0);
IntMasterEnable();
先使能相应功能的中断,再使能总系统中断。
3.4编写中断服务函数
中断服务函数的编写基本上是先检验并清除中断标志位,接下来才开始写你要在中断中执行什么语句。
void UART0IntHandler(void)
{
uint32_t ui32IntStatus;
ui32IntStatus = UARTIntStatus(UART0_BASE, true);
UARTIntClear(UART0_BASE, ui32IntStatus);//先清除中断标志位
//。。。这里写要执行什么语句
}
中断标志位清除这一步非常重要,中断触发条件后做的一件事就是置了一个中断标志位,相当于告诉系统该前往中断服务函数了,系统通过检测中断标志位,才跳向中断服务函数,如果中断服务函数执行完但中断标志位没有清掉的话,就会一直跳向中断,这样通常单片机就会产生奇奇怪怪的现象。
4.
其他函数:
UARTCharPut(UART0_BASE, 'o');
UARTCharPut(UART0_BASE, 0x0a);
发送一个字符,第一个参数是哪个串口发,第二个参数是发什么,这里可以是0-128的ascii码。
UARTCharsAvail(UART0_BASE);
检测缓存区中是否有字符存在,有的话返回true,没有的话返回false;通常用在中断里
UARTCharGetNonBlocking(UART0_BASE);
从指定的串口接收一个字符,nonblocking指如果没有字符则不会等待,直接返回-1
通常与UARTCharsAvail(UART0_BASE);搭配使用
二、配置思路
1.配置系统时钟
2.配置相关功能的时钟
3.配置io口功能复用
4.配置串口基础信息
5.配置中断
三、完整代码
#include "include.h"//可在本系列第五篇找到include.h内容
void UART0IntHandler(void)
{
uint32_t ui32IntStatus;
uint8_t ui8RxBuffer[4];
uint8_t i = 0;
ui32IntStatus = UARTIntStatus(UART0_BASE, true);
UARTIntClear(UART0_BASE, ui32IntStatus);
while (UARTCharsAvail(UART0_BASE))
{
ui8RxBuffer[i++] = (uint8_t) UARTCharGetNonBlocking(UART0_BASE);
if (i > 3)
{
break;
}
}
if (ui8RxBuffer[0] == 'a' && ui8RxBuffer[1] == 'b' && ui8RxBuffer[2] == 'c'
&& ui8RxBuffer[3] == 'd')
{
UARTCharPut(UART0_BASE, 'o');
UARTCharPut(UART0_BASE, 'k');
UARTCharPut(UART0_BASE, 0x0d);
UARTCharPut(UART0_BASE, 0x0a);
}
}
void main(void)
{
SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
SYSCTL_XTAL_16MHZ);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA))
;
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0))
;
GPIOPinConfigure(GPIO_PA0_U0RX);
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0);
GPIOPinConfigure(GPIO_PA1_U0TX);
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_1);
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
UARTFIFOEnable(UART0_BASE);
UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX2_8, UART_FIFO_RX2_8);
UARTIntEnable(UART0_BASE, UART_INT_RX);
IntPrioritySet(INT_UART0, 0x0);
UARTIntRegister(UART0_BASE, UART0IntHandler);
IntEnable(INT_UART0);
IntMasterEnable();
while (1)
{
}
}
四、具体现象
while (UARTCharsAvail(UART0_BASE))
{
ui8RxBuffer[i++] = (uint8_t) UARTCharGetNonBlocking(UART0_BASE);
if (i > 3)
{
break;
}
}
分析代码的这一段可知,每次中断都是四个字符触发一次,用while可以直接全部接收到数组中;
if (ui8RxBuffer[0] == 'a' && ui8RxBuffer[1] == 'b' && ui8RxBuffer[2] == 'c'
&& ui8RxBuffer[3] == 'd')
{
UARTCharPut(UART0_BASE, 'o');
UARTCharPut(UART0_BASE, 'k');
UARTCharPut(UART0_BASE, 0x0d);
UARTCharPut(UART0_BASE, 0x0a);
}
接着如果发送的是abcd的话,会执行在屏幕上打印okde语句。
如图
串口这一块写的很基础,学会迁移运用很重要。