在通信领域内,有两种数据通信方式:并行通信和串行通信,串口的数据传输是以串行方式进行的,串口在数据通信中,一次只传输一个比特的数据,串行数据的传输速度用bps或波特率来描述。
常用术语:
1.单工(simplex):仅能进行一个方向的数据传输。
2.半双工(half duplex):数据可以在两个方向上进行传输,但这种传输绝对不能同时进行(同步)
3.全双工(full duplex):能够在两个方向同时进行数据的传输(异步)。
我们的开发板和PC之间输出数据都是靠高低电平来实现,我们就需要约定一个串口协议,包括起始位,停止位,数据长度,校验位(奇偶校验),以及bps,通过这个协议我们通过UART进行高低电平检测达到通信传输数据的目的。
硬件流控制:
来看看实际收发数据是如何进行的,首先我们发射端和接收端缓存区工作方式需要设置为轮询或中断或DMA(内存访问,不安全),并且为了防止数据发送产生粘包现象,我们需要对发送缓存区进行判空操作,对接受缓存区进行判满操作,可以通过UTRSTAT寄存器的状态来判断。
上图就是我们传输的全过程图,我们可以再芯片手册中找到对应的寄存器进行设置,最后我会通过一个代码实例来展示。
可以看到在UTRSTAT寄存器的第1位第0位分别表示发送区和接收区的状态,通过位运算我们就可以判断当前发射区和接收区的状态,从而让CPU知道什么时候该写数据接数据。
现在我们可以通过在超级终端中输入对应的字符来控制led灯的亮和灭:
#include <string.h>
#include <stdio.h>
void delay(int m)
{
int i, j;
for(i = 0; i < m; i++)
for(j = 0; j < 256; j++);
}
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON *(volatile unsigned int *)0x13820000
#define UBRDIV *(volatile unsigned int *)0x13820028
#define UFRACVAL *(volatile unsigned int *)0x1382002c
#define UCON *(volatile unsigned int *)0x13820004
#define UTRSTAT *(volatile unsigned int *)0x13820010
#define UTXH *(volatile unsigned int *)0x13820020
#define URXH *(volatile unsigned int *)0x13820024
#define GPX2CON *(volatile unsigned int *)0x11000c40
#define GPX2DAT *(volatile unsigned int *)0x11000c44
void led2_init()
{
// GPX2CON = GPX2CON & 0x0fffffff | (1 << 28)
GPX2CON = GPX2CON & (~(0xf << 28)) | ( 0x1 << 28 );
}
void led2_on()
{
GPX2DAT = GPX2DAT | (1 << 7);
}
void led2_off()
{
GPX2DAT = GPX2DAT & (~(1 << 7));
}
void gpio_init()
{
//设置GPA1_0复用 UART_2_RXD功能,GPA1_1复用 UART_2_TXD功能
// GPA1CON = GPA1CON & ( ~(0xff << 0)) | (0x2 << 0) | (0x2 << 4);
GPA1CON = GPA1CON & ( ~(0xff << 0)) | (0x22 << 0);
}
void uart_init()
{
//设置串口协议:数据位(8)、停止位(1)、奇偶校验位(无): 0000011
ULCON |= 0x3;
//设置波特率:115200
// DIV_VAL = (PCLK_UART)/ (bps *16) -1
// = 100MHZ / (115200*16)-1
// = 100000000 / 115200/16-1
// = 53.25
// UBRDIV = 53
// UFRACVAL = 0.25*16 = 4
UBRDIV = 53;
UFRACVAL = 4;
//设置发送和接受数据的 工作模式: 中断或轮询 0101
UCON = 0x05;
}
void uart_send(char ch)
{
// while( UTRSTAT & (0x1 << 1) != 0x2 );
while( !(UTRSTAT & (0x1 << 1)) );
UTXH = ch;
}
char uart_recv()
{
// while( UTRSTAT & (0x1) != 1);
while( !(UTRSTAT & (0x1)));
return URXH & 0xff;
}
void send(char *p)
{
while(*p){
uart_send(*p++);
}
}
int myStrcmp(char *s1, char *s2)
{
}
int main()
{
char *p = "hello world";
gpio_init();
uart_init();
led2_init();
send(p);
send("\r\n");
int i = 0;
while(1){
char buf[12] = {0};
for(i = 0; i < 12; i++){
buf[i] = uart_recv();
if( buf[i] == ' ')
break;
uart_send(buf[i]);
}
send("\r\n");
if ( myStrcmp(buf, "led_on") == 0){
led2_on();
} else if( myStrcmp(buf, "led_off") == 0){
led2_off();
}
}
}
主要还是通过芯片手册找到对应的寄存器进行相应的工作模式设置!!!
看门狗
看门狗,是一个定时器电路,一般有一个输入,叫喂狗,一个输出到MCU的RST端,MCU正常工作的时候,每隔一段时间输出一个信号到喂狗段,给WDT清零,如果超过规定的时间不喂狗,WDT定时超时,就会给出一个复位信号到MCU,让MCU复位,防止MCU死机,看门狗的作用就是防止程序发生死循环,或者说程序跑飞。
工作原理:
在系统运行以后就启动了看门狗的计数器,看门狗就会开始自动计数,到了一定的时间还不去喂狗,那么看门狗计数器就会溢出导致看门狗中断,造成系统复位。
一般我们需要对看门狗进行设置,需要通过下面这张图的步骤:
看一段代码:这段代码详细介绍了看门狗的设置过程。
void delay(int m)
{
int i, j;
for(i = 0; i < m; i++)
for(j = 0; j < 256; j++);
}
#define GPD0CON *(volatile unsigned int *)0x114000a0
#define GPD0DAT *(volatile unsigned int *)0x114000a4
//蜂鸣器设置
void beep_init()
{
GPD0CON = GPD0CON & (~(0xf << 0)) | ( 0x1 << 0 );
}
void beep_on()
{
GPD0DAT = GPD0DAT | (1 << 0);
}
void beep_off()
{
GPD0DAT = GPD0DAT & (~(1 << 0));
}
/**
* wdt频率 = plck / 预分频 / 二分频
* 定时长 = (比较寄存器值 - 计数寄存器值) / wdt频率
*
* */
#define WTCON *(volatile unsigned int *)0x10060000
#define WTDAT *(volatile unsigned int *)0x10060004
#define WTCNT *(volatile unsigned int *)0x10060008
void wdt_init()
{
//设置预分频值 255
WTCON |= 0xff << 8;
//设置二分频值 128
WTCON |= 0x3 << 3;
//设置 WTCNT 计数寄存器值 1000
WTCNT = 10000;
//设置 WTDAT 数据寄存器值
WTDAT = 10000;
//设置 超时 产生复位信号 0x1
WTCON |= 0x1;
//启动 wdt的 定时器
WTCON |= 1 << 5;
}
int main()
{
wdt_init();
beep_init();
while(1){
//喂狗,内部自动刷新cnt寄存器
//WTDAT = 10000; //只有超时触发终端的时候,向DAT寄存器写数据,会自动搬移到 CNT寄存器
WTCNT = 10000; //CNT未 溢出产生复位信号时,必须通过 CNT重新装载,才能保证不产生复位信号
beep_on();
delay(5);
beep_off();
delay(15);
}
}