stm32——串口

一.stm32串口的简介

        串口是一种应用十分广泛的通讯接口,可以实现两个设备的相互通讯。

        在我们学习的过程中我们会发现串口有usart还有uart这些说法,一开始我以为是少打了一个字母,后来才发现不是这样的。处理器和外部设备通讯有两种方式:串行通讯和并行通讯。而串行数据通讯有两种基本的方式,一种是同步通讯还有一种是异步通讯。uart使用的是异步的通讯方式,usart则是在uart的基础上增加了时钟。

注意:简单双向串口通信有两根通信线(发送端TX和接收端RX)TX与RX要交叉连接

  二.USART简介

        USART 是 Universal Synchronous/Asynchronous Receiver/Transmitter 的缩写,即通用同步/异步收发传输器。

串口通讯的基本参数:

·波特率:串口通讯的速率

·起始位:标志一个数据帧的开始,且固定是低电平

·数据位:数据帧的有效载荷,1为高电平,0为低电平

·校验位:   用于数据验证,根据数据位计算而来(奇偶校验)

·停止位:用于数据帧间隔,固定为高电平

注意:这里我们 可以看到一个为8位数据一个为9位数据,他们的校验位就是一个用无校验,一个用奇偶校验。

奇偶检验知识点:UART串口校验方式(无校验、奇偶校验、固定校验)_uart校验位-CSDN博客

三.stm32的外设USART

        USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。

stm32内USART的接收发送框图:

 stm32的波特率发生器:

        USART的波特率由波特率寄存器的BRR里的DIV确定

        波特率的计算公式:      波特率 = fPCLK2/1 / (16 * DIV)

四.USART的应用 

         在这里我们主要介绍USART的三种应用,串口基本发送,DMA发送,中断收发。

stm32中有多个串口收发引脚,他们对应的功能也不一样,在数据手册里面有介绍。

void    USART1_Init (uint32_t baudrate);                      // 初始化串口的GPIO、通信参数配置、中断优先级; (波特率可设、8位数据、无校验、1个停止位)
uint8_t USART1_GetBuffer (uint8_t* buffer, uint8_t* cnt);     // 获取接收到的数据
void    USART1_SendData (uint8_t* buf, uint8_t cnt);          // 通过中断发送数据,适合各种数据
void    USART1_SendString (char* stringTemp);                 // 通过中断发送字符串,适合字符串,长度在256个长度内的
void    USART1_SendStringForDMA (char* stringTemp) ;          // 通过DMA发送数据,适合一次过发送数据量特别大的字符串,省了占用中断的时间
/******************************************************************************
 * 函  数: vUSART1_Init
 * 功  能: 初始化USART1的GPIO、通信参数配置、中断优先级 
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/  
void USART1_Init(uint32_t baudrate)
{   
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;    
    
    // 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;                           // 使能USART1时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                             // 使能GPIOA时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚,配置为复用推挽工作模式
    GPIO_Init (GPIOA, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING  ;        // RX引脚,由于板子上为一主多从电路,故选择复用开漏模式
    GPIO_Init (GPIOA, &GPIO_InitStructure);    

    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority=2 ;       // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                              
    
    //USART 初始化设置
    USART_DeInit(USART1); 
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART1, &USART_InitStructure);                       // 初始化串口
    
    USART_ITConfig(USART1, USART_IT_TXE , DISABLE );
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);                  // 使能空闲中断
    
    USART_Cmd(USART1, ENABLE);                                      // 使能串口, 开始工作  
    
    USART1->SR = ~(0x00F0);                                         // 清理中断
    
    xUSART.USART1InitFlag =1;                                       // 标记初始化标志
    xUSART.USART1ReceivedNum =0;                                    // 接收字节数清零
   
    printf("\r\r\r=========== 魔女开发板 STM32F103 外设初始报告 ===========\r");      
    printf("USART1初始化配置      接收中断、空闲中断, 发送中断\r");    
}

/******************************************************************************
 * 函  数: USART1_IRQHandler
 * 功  能: USART1的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 *           
******************************************************************************/
static uint8_t U1TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U1TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U1TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART1_IRQHandler(void)           
{     
    static uint16_t cnt=0;                                           // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U1_RX_BUF_SIZE];                          // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;
    
    // 接收中断
    if(USART1->SR & (1<<5))                                          // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if((cnt>=U1_RX_BUF_SIZE))//||(xUSART.USART1ReceivedFlag==1)) // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {                                                            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART1->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        } 
        RxTemp[cnt++] = USART1->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }
    
    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if(USART1->SR & (1<<4))                                          // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {         
        xUSART.USART1ReceivedNum  = 0;                               // 把接收到的数据字节数清0     
        memcpy(xUSART.USART1ReceivedBuffer, RxTemp, U1_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.USART1ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedNum中;     
        cnt=0;                                                       // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp ,0, U1_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收   
        USART1 ->SR;  USART1 ->DR;                                   // 清零IDLE中断标志位!! 序列清零,顺序不能错!!      
     }     

    // 发送中断
    if ((USART1->SR & 1<<7) && (USART1->CR1 & 1<<7))                 // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {                
        USART1->DR = U1TxBuffer[U1TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;        
        if(U1TxCounter == U1TxCount )
            USART1->CR1 &= ~(1<<7);                                  // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }    
}  

/******************************************************************************
 * 函  数: vUSART1_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数 
 * 返回值: 0_没有接收到新数据, 1_接收到新数据
 ******************************************************************************/  
uint8_t USART1_GetBuffer(uint8_t* buffer, uint8_t* cnt)
{    
    if(xUSART.USART1ReceivedNum)               // 判断是否有新数据
    {    
        memcpy(buffer, xUSART.USART1ReceivedBuffer, xUSART.USART1ReceivedNum );     // 把新数据复制到指定位置
        memset(xUSART.USART1ReceivedBuffer ,0, U1_RX_BUF_SIZE);                     // 接收数据缓存数组,清零; 准备下一次的接收   
        *cnt = xUSART.USART1ReceivedNum;       // 把新数据的字节数,存放指定变量   
        xUSART.USART1ReceivedNum = 0;          // 接收标记置0
        return 1;                              // 返回1, 表示接收到新数据
    }
    return 0;                                  // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUSART1_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/  
void USART1_SendData(uint8_t* buf, uint8_t cnt)
{
    for(uint8_t i=0; i<cnt; i++) 
        U1TxBuffer[U1TxCount++] = buf[i];
     
    if((USART1->CR1 & 1<<7) == 0 )         // 检查发送缓冲区空置中断(TXEIE)是否已打开
        USART1->CR1 |= 1<<7;             
}

/******************************************************************************
 * 函  数: vUSART1_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/  
void USART1_SendString(char* stringTemp)
{
    u16 num=0;                                   // 字符串长度
    char* t=stringTemp ;                         // 用于配合计算发送的数量    
    while(*t++ !=0)  num++;                      // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位      
    USART1_SendData((u8*)stringTemp, num+1);     // 调用函数完成发送,num+1:字符串以0结尾,需多发一个:0   
}

/******************************************************************************
 * 函  数: vUSART1_SendStringForDMA
 * 功  能: UART通过DMA发送数据,省了占用中断的时间
 *         【适合场景】字符串,字节数非常多,
 *         【不 适 合】1:只适合发送字符串,不适合发送可能含0的数值类数据; 2-时间间隔要足够
 * 参  数: char strintTemp  要发送的字符串首地址
 * 返回值: 无
 ******************************************************************************/  
void USART1_SendStringForDMA(char* stringTemp) 
{
    static u8 Flag_DmaTxInit=0;                  // 用于标记是否已配置DMA发送
    u32   num = 0;                               // 发送的数量,注意发送的单位不是必须8位的    
    char* t =stringTemp ;                        // 用于配合计算发送的数量    
    
    while(*t++ !=0)  num++;                      // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位           

    while(DMA1_Channel4->CNDTR > 0);             // 重要:如果DMA还在进行上次发送,就等待; 得进完成中断清标志,F4不用这么麻烦,发送完后EN自动清零
    if( Flag_DmaTxInit == 0)                     // 是否已进行过USAART_TX的DMA传输配置
    {   
        Flag_DmaTxInit  = 1;                     // 设置标记,下次调用本函数就不再进行配置了
        USART1 ->CR3   |= 1<<7;                  // 使能DMA发送
        RCC->AHBENR    |= 1<<0;                  // 开启DMA1时钟  [0]DMA1   [1]DMA2        
 
        DMA1_Channel4->CCR   = 0;                // 失能, 清0整个寄存器, DMA必须失能才能配置
        DMA1_Channel4->CNDTR = num;              // 传输数据量   
        DMA1_Channel4->CMAR  = (u32)stringTemp;  // 存储器地址 
        DMA1_Channel4->CPAR  = (u32)&USART1->DR; // 外设地址      

        DMA1_Channel4->CCR |= 1<<4;              // 数据传输方向   0:从外设读   1:从存储器读
        DMA1_Channel4->CCR |= 0<<5;              // 循环模式       0:不循环     1:循环
        DMA1_Channel4->CCR |= 0<<6;              // 外设地址非增量模式
        DMA1_Channel4->CCR |= 1<<7;              // 存储器增量模式
        DMA1_Channel4->CCR |= 0<<8;              // 外设数据宽度为8位
        DMA1_Channel4->CCR |= 0<<10;             // 存储器数据宽度8位
        DMA1_Channel4->CCR |= 0<<12;             // 中等优先级
        DMA1_Channel4->CCR |= 0<<14;             // 非存储器到存储器模式    
    }    
    DMA1_Channel4->CCR  &= ~((u32)(1<<0));       // 失能,DMA必须失能才能配置
    DMA1_Channel4->CNDTR = num;                  // 传输数据量
    DMA1_Channel4->CMAR  = (u32)stringTemp;      // 存储器地址      
    DMA1_Channel4->CCR  |= 1<<0;                 // 开启DMA传输   
} 



//  printf   //
/******************************************************************************
 * 功  能: printf函数支持代码
 *         【特别注意】加入以下代码, 使用printf函数时, 不再需要选择use MicroLIB     
 * 参  数: 
 * 返回值:
 * 备  注: 魔女开发板团队  资料存放Q群:1126717453      最后修改_2020年07月15日
 ******************************************************************************/  
//加入以下代码,支持printf函数,而不需要选择use MicroLIB     
#pragma import(__use_no_semihosting)                
struct __FILE       { int handle; };         // 标准库需要的支持函数
FILE __stdout;                               // FILE 在stdio.h文件
void _sys_exit(int x) {    x = x; }          // 定义_sys_exit()以避免使用半主机模式

int fputc(int ch, FILE *f)                   // 重定向fputc函数,使printf的输出,由fputc输出到UART,  这里使用串口1(USART1)
{ 
    #if 1                                    // 方式1-使用常用的poll方式发送数据,比较容易理解,但等待耗时大  
        while((USARTx_DEBUG->SR & 0X40)==0); // 等待上一次串口数据发送完成 
        USARTx_DEBUG->DR = (u8) ch;          // 写DR,串口1将发送数据 
        return ch;
    #else                                    // 方式2-使用queue+中断方式发送数据; 无需像方式1那样等待耗时,但要借助已写好的函数、环形缓冲
        uint8_t c[1]={(uint8_t)ch};    
        if(USARTx_DEBUG == USART1)    vUSART1_SendData (c, 1);
        if(USARTx_DEBUG == USART2)    vUSART2_SendData (c, 1);
        if(USARTx_DEBUG == USART3)    vUSART3_SendData (c, 1);
        if(USARTx_DEBUG == UART4)     vUART4_SendData  (c, 1);
        if(USARTx_DEBUG == UART5)     vUART5_SendData  (c, 1);
        return ch;
    #endif    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值