串口通信是非常非常常见的一种通信方式,必须掌握的。可以从如下几个方面掌握串口通信:
在了解原理之前,我们先看看串口要如何使用,如下图,只要选择正确的串口号,把收发双方的波特率、校验位、数据位、停止位配置成一致,这么就可以实现双方通信。
那么配置的这些参数分别代表什么意思呢?
串口号:唯一标识一个串口,当设备存在多个串口时,可以用其标识每个串口。
波特率:每秒钟传输的数据位数。表示数据传输的速率,单位bps(位每秒)。比如115200bps就表示1s可以传输115200bits的数据。
校验位:
even 每个字节传送整个过程中bit为1的个数是偶数个(校验位调整个数)
odd 每个字节穿送整个过程中bit为1的个数是奇数个(校验位调整个数)
none 没有校验位
space 校验位总为0
mark 校验位总为1
数据位:5678共4个选择,这是历史原因,如下
5:用于电报机传26个英文字母,5位足以
6:用于电报机,识别大小写字母,增加一个大小写位
7:用于电脑,ASCII码7位
8:用于电脑,DBCS码用于兼容ASCII和支持中文双字节
停止位:
停止位是按长度来算的。串行异步通信从计时开始,以单位时间为间隔(一个单位时间就是波特率的倒数),依次接受所规定的数据位和奇偶校验位,并拼装成一个字符的并行字节;此后应接收到规定长度的停止位“1”。所以说,停止位都是“1”,1.5是它的长度,即停止位的高电平保持1.5个单位时间长度。一般来讲,停止位有1,1.5,2个单位时间三种长度。
下面我们看下串行协议的帧格式,如图
一个帧由4部分组成,起始位+数据位+校验位+停止位,正好跟上面的配置一一对应,其中,起始位必须是低电平,停止位必须是高电平。
至此,也大致明白串口是怎么回事了。
下面几种都是串口,只是电平标准不同,导致其应用场景存在差异,通信协议和配置都是相同的,通信原理是相同的,软件实现相同,硬件电路存在差异。
TTL:
接线方式如图
高电平表示逻辑1, 低电平表示逻辑零
![1536645105294](assets/1536739271596.png)
RS232和RS485对比
我们知道串口的作用是为CPU和其它设备之间提供通信,本质上是把数据从其他设备搬移到自身MCU的内存中去,如上图,MCU为实现串口功能会做如上的模块划分。
GPIO
串口总线状态,默认是高电平,所以Tx应该是上拉输出,Rx应该是浮空输入。
移位器
我们知道串口是一位位传输的,所以移位器即可以实现串口的收发。
数据寄存器
用于存储将要发送和接收的数据,其实只要收发共用一个字节就足以。
时钟
上述的运行过程都需要在固定时钟下才能正确运行,例如波特率。
数据由寄存器搬移到内存
状态寄存器
配置寄存器
上述情况那么多,代表不同的配置,肯定需要几组配置寄存器。例如,中断的使能控制等
如果明白了原理,那么自然就知道该如何配置一个串口了,无非就是从芯片手册中找到相应的寄存器进行配置而已。
在”串口发送“例子中,已经接触了串口的发送功能,现在我们把这个例子再度深入,实现串口的接收功能。实现一个回显功能,即PC通过串口向GD32写入数据,然后GD32把数据原封不动返回给PC。
VOID DRV_UART1_PollTest(VOID)
{
U8 ch = 0;
while (1)
{
if (USART_GetBitState(USART1, USART_FLAG_RBNE) != RESET)
{
ch = (U8)USART_DataReceive(USART1);
UART1_SendChar(ch);
}
}
}
VOID DRV_UART1_PollInit(VOID)
{
UART1_GpioInit();
UART1_Config();
USART_Enable(USART1, ENABLE);
}
效果如图
注:中断优先级部分,我会抽单独章节分析。
必须注意下面这两个函数的区别,
USART_GetBitState(USART1, USART_FLAG_RBNE); /* 非中断使用 */
USART_GetIntBitState(USART1, USART_INT_RBNE);/* 中断内使用 */
中断方式处理代码如下:
VOID USART1_IRQHandler(VOID)
{
if (USART_GetIntBitState(USART1, USART_INT_RBNE) != RESET)
{
if (gUart1RxCount >= DRV_UART1_BUFLEN)
{
memset(gUart1RxBuf, 0, sizeof(gUart1RxBuf));
gUart1RxCount = 0;
}
gUart1RxBuf[gUart1RxCount] = (U8)USART_DataReceive(USART1);
gUart1RxCount++;
}
if (USART_GetIntBitState(USART1, USART_INT_IDLEF) != RESET)
{
gUart1RxBufFlag++;
}
}
VOID DRV_UART1_InterruptTest(VOID)
{
U8 rxCount = 0;
while (1)
{
if (gUart1RxBufFlag > 0)
{
for (rxCount = 0; rxCount < gUart1RxCount; rxCount++)
{
UART1_SendChar(gUart1RxBuf[rxCount]);
}
memset(gUart1RxBuf, 0, sizeof(gUart1RxBuf));
gUart1RxCount = 0;
gUart1RxBufFlag = 0;
}
}
}
VOID DRV_UART1_InterruptInit(VOID)
{
UART1_GpioInit();
UART1_Config();
UART1_NvicConfiguration();
USART_Enable(USART1, ENABLE);
USART_INT_Set(USART1, USART_INT_RBNE, ENABLE);
USART_INT_Set(USART1, USART_INT_IDLEF, ENABLE);
}
注:DMA细节我会抽单独章节分析,此处只写一个DMA轮询方式的例子。
static VOID UART1_DmaRxConfig(IN U8 *buf, IN U32 len)
{
DMA_InitPara DMA_InitStructure;
DMA_Enable(DMA1_CHANNEL5, DISABLE);
/* USART1 RX DMA1 Channel (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA1_CHANNEL5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (U32) &(USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (U32)buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PERIPHERALSRC;
DMA_InitStructure.DMA_BufferSize = len;
DMA_InitStructure.DMA_PeripheralInc = DMA_PERIPHERALINC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEMORYINC_ENABLE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PERIPHERALDATASIZE_BYTE;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MEMORYDATASIZE_BYTE;
DMA_InitStructure.DMA_Mode = DMA_MODE_NORMAL;
DMA_InitStructure.DMA_Priority = DMA_PRIORITY_VERYHIGH;
DMA_InitStructure.DMA_MTOM = DMA_MEMTOMEM_DISABLE;
DMA_Init(DMA1_CHANNEL5, &DMA_InitStructure);
DMA_Enable(DMA1_CHANNEL5, ENABLE);
}
VOID DRV_UART1_DmaInit(VOID)
{
UART1_GpioInit();
UART1_Config();
RCC_AHBPeriphClock_Enable(RCC_AHBPERIPH_DMA1, ENABLE);
UART1_DmaRxConfig(gUart1RxBuf, DRV_UART1_BUFLEN);
USART_Enable(USART1, ENABLE);
USART_DMA_Enable(USART1, (USART_DMAREQ_TX | USART_DMAREQ_RX), ENABLE);
}
static VOID UART1_DmaSend(IN U8 *buf, IN U32 len)
{
DMA_InitPara DMA_InitStructure;
DMA_Enable(DMA1_CHANNEL4, DISABLE);
/* USART1_Tx_DMA_Channel (triggered by USART1 Tx event) Config */
DMA_DeInit(DMA1_CHANNEL4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (U32) &(USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (U32)buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PERIPHERALDST;
DMA_InitStructure.DMA_BufferSize = len;
DMA_InitStructure.DMA_PeripheralInc = DMA_PERIPHERALINC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEMORYINC_ENABLE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PERIPHERALDATASIZE_BYTE;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MEMORYDATASIZE_BYTE;
DMA_InitStructure.DMA_Mode = DMA_MODE_NORMAL;
DMA_InitStructure.DMA_Priority = DMA_PRIORITY_VERYHIGH;
DMA_InitStructure.DMA_MTOM = DMA_MEMTOMEM_DISABLE;
DMA_Init(DMA1_CHANNEL4, &DMA_InitStructure);
DMA_Enable(DMA1_CHANNEL4, ENABLE);
while (DMA_GetBitState(DMA1_FLAG_TC4) == RESET)
{
}
}
VOID DRV_UART1_DmaTest(VOID)
{
while (1)
{
if (USART_GetBitState(USART1, USART_FLAG_IDLEF) != RESET)
{
UART1_DmaSend(gUart1RxBuf, DRV_UART1_BUFLEN);
memset(gUart1RxBuf, 0, DRV_UART1_BUFLEN);
UART1_DmaRxConfig(gUart1RxBuf, DRV_UART1_BUFLEN);
USART_DataReceive(USART1); /* 清除USART_FLAG_IDLEF */
}
}
}
串口是一种非常常见的通信总线,必须掌握。如果上面的原理和例子理解了,我相信用GPIO口虚拟一个窗口并不是什么难事。
https://github.com/YaFood/GD32F103/tree/master/TestUART