外设篇—————串口
标准库
在串口屏,WiFi模块时候经常会用到串口,所以串口还是需要了解一下原理,才能更好的借鉴别人代码(doge)。
初窥门径
当数据发送时候,单片机向发送数据寄存器(TDR)写数据,TDR里面的数据会自动的转移到发送移位寄存器,然后通过单片机的TX口发送出去。
当数据接收时候,数据通过单片机RX口先到移位寄存器,通过移位寄存器转移到接收寄存器(RDR)中,然后被单片机读取到内存中。
我们可以看到图中有TDR和RDR两个数据寄存器,这两个寄存器会组合成一个寄存器DR。移位寄存器是不用我们操作的,还有一个重要的状态寄存器(SR),下面对状态寄存器详细介绍。
状态寄存器其中的状态标志位有:
#define USART_FLAG_CTS ((uint16_t)0x0200)
#define USART_FLAG_LBD ((uint16_t)0x0100)
#define USART_FLAG_TXE ((uint16_t)0x0080)
//TDR寄存器为空标志
#define USART_FLAG_TC ((uint16_t)0x0040)
//发送完成标志
#define USART_FLAG_RXNE ((uint16_t)0x0020)
//RDR不为空标志
#define USART_FLAG_IDLE ((uint16_t)0x0010)
//数据线空闲标志
#define USART_FLAG_ORE ((uint16_t)0x0008)
#define USART_FLAG_NE ((uint16_t)0x0004)
#define USART_FLAG_FE ((uint16_t)0x0002)
#define USART_FLAG_PE ((uint16_t)0x0001)
这个寄存器作用就是告诉我们串口通讯的情况,发没发完,发送的数据对不对,有没有数据正准备要发送等等···,我们通过对这些状态的判断来监测传输情况和进行一些逻辑算法来增加传输可靠性。
略知一二
常用的通讯函数
串口发送函数
上代码
void UART1_Send_Byte(u8 Data) //发送一个字节
{
USART_GetFlagStatus(UART1, USART_FLAG_TC);
USART_SendData(UART5,Data);
while( USART_GetFlagStatus(UART1, USART_FLAG_TC) == RESET );
}
void UART5_Send_String(u8 *Data) //发送字符串
{
while(*Data)
UART5_Send_Byte(*Data++);
}
串口接收函数
常用方案,利用中断来接收数据,上代码
void USART1_IRQHandler()
{
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET) //中断产生
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志
Uart1_Buffer[Uart1_Rx_Num] = USART_ReceiveData(USART2);
Uart1_Rx_Num++;
}
注:在使用串口接收中断要进行串口中断配置,代码如下
NVIC_InitTypeDef NVIC_InitStructure;
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
现在已经可以使用串口进行简单的数据通讯了,下一步要按照我们使用协议的需求进行通讯。
粗通皮毛
自定义协议传输
例如与串口屏通讯通讯时,接收到串口屏发来的数据为
这时我们就不能随便接收数据了,要判断发过来的数据是不是我们想收到的有效数据,如上只有数据帧头部为EE,尾部为 FF,FC,FF,FF的数据才是我们想要的。
实现代码如下
#define uart_buffer_size 100
unsigned char uart_rec_buffer[uart_buffer_size];
unsigned char index_uart=0; //接收数据位移指针
unsigned char uart_buffer_head_ok;//数据头标志
unsigned char cmd_in=0; //接收完成标志位
void USART1_IRQHandler(void)
{
unsigned char c;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit( USART1 , USART_IT_RXNE );
//清楚状态标志位,等待下次数据触发中断
c=USART1->DR;
if(c==0xEE) //如果收到的数据头是0xee,进入下面循环
{
uart_buffer_head_ok=1; //数据头正确
index_uart=0; //指针指向数组的第一个元素
}
if(uart_buffer_head_ok==1) //当再有数据来时进入此循环
{
uart_rec_buffer[index_uart++]=c; //以此保存数据到缓存区
if(index_uart>=4) //判断数据尾帧
{
if((uart_rec_buffer[index_uart-1]==0xFF)
&&(uart_rec_buffer[index_uart-2]==0xFF)
&&(uart_rec_buffer[index_uart-3]==0xFC)
&&(uart_rec_buffer[index_uart-4]==0xFF)) //帧尾正确
{
uart_buffer_head_ok=0; //标志位清零,等待下次接收
index_uart=0;
cmd_in=1;
}
}
}
else if(uart_buffer_head_ok==0)
{
index_uart=0;
}
if(index_uart>=uart_buffer_size) //如果发生溢出,接收指针清零从新接收
index_uart=0;
}
}
这样可以实现按照我们的需求接收数据,并且提高了可靠性,但是如果传来的数据比较多时,会频繁的触发中断,造成CPU负担,此时我们可以采用DMA来搬运数据,中断采用空闲中断(IDLE),
空闲中断是指没有数据穿过来时候数据线处于空闲状态,此时当一帧数据来的时候才会触发中断。
DMA空闲接收代码如下
#define DMA_USART1_RECEIVE_LEN 18
void USART1_IRQHandler(void)
{
u32 r= 0 ,i = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
USART1->SR;
USART1->DR; /通过读寄存器来清USART_IT_IDLE标志
DMA_Cmd(DMA1_Channel5,DISABLE); //关闭DMA,防止再次来数据
r= DMA_USART1_RECEIVE_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);
//接收的字符串长度=设置的接收长度-剩余DMA缓存大小
for (i = 0;i < r;i++)
{
Uart2_Buffer[i] = USART1_RECEIVE_DMABuffer[i];
}
//设置传输数据长度
DMA_SetCurrDataCounter(DMA1_Channel5,DMA_USART1_RECEIVE_LEN);
DMA_Cmd(DMA1_Channel5,ENABLE); //打开DMA
}
}
总结:简单说明了串口通讯过程,列举了常用的串口配置函数,在不同的场景下再进行适当修改就好了。多多参考别人的代码学习和改进,下一个境界登堂入室就不远了。