CKS32F4xx系列产品串口通信

cathy的头像
cathy 发布于:周一, 04/03/2023 - 14:10 ,关键词:

串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,大部分电子设备都支持该通讯方式。串口在CKS32上应用最多的莫过于“打印”程序信息,一般在硬件设计时都会预留一个串口连接电脑,用于在调试程序时可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、指出程序运行出错位置等等。

CKS32F4xx系列产品串口介绍

CKS32F4xx系列最多可提供6路串口,其中四个USART和两个UART。USART和UART在引脚上的区别是:UART只有RX和TX引脚,而USART除了这两个引脚之外,还有流控引脚RTS和CTS,以及时钟引脚SCLK。CKS32F4xx系列产品的USART1和USART6时钟来源于APB2总线时钟,其最大频率为84MHz,因此这两个串口的通信速度最高可达10.5Mbit/s。而其它四个的时钟来源于APB1总线时钟,其最大频率为42MHz,因此这四个串口的通信速度最高可达5.25Mbit/s。因为USART有SCLK引脚,因此CKS32F4xx系列产品的USART具有同步通信功能,而UART只有异步通信功能。同时USART还支持ISO7816的智能卡接口。但是当USART和UART都用在异步通信的时候,两者是没有什么区别的。CKS32F4xx系列的6个串口都支持DMA传输。

CKS32F4xx系列产品的串口在发送数据时,当发送使能位TE置1之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),然后就可以往USART_DR寄存器写入要发送的数据。在写入最后一个数据后,需要等USART状态寄存器(USART_SR)的TC位为1,表示数据传输完成,如果USART_CR1寄存器的TCIE位置1,将产生中断。串口发送的一个字符帧由三个部分组成:起始位+数据帧+停止位。起始位是一个位周期的低电平;数据帧就是我们要发送的8位或9位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。停止位时间长短是可以通过USART控制寄存器2(USART_CR2)的STOP[1:0]位控制,可选0.5个、1个、1.5个和2个停止位。默认使用1个停止位。2个停止位适用于正常USART模式、单线模式和调制解调器模式。0.5个和1.5个停止位用于智能卡模式。

CKS32F4xx系列产品的串口在接收数据时,需要先将USART_CR1寄存器的RE 位置1,使能USART接收,使得接收器在RX线开始搜索起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果 USART_CR2寄存器的RXNEIE置1的话可以产生中断。

CKS32F4xx系列产品控制器的USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数据帧加上1位的校验位总共9位,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,则可以产生奇偶校验中断。使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧 +校验位+停止位。

USART有多个中断请求事件,具体如下表所示:在串口的中断服务函数里,通过对这些中断事件标志的检测,就可以判断出是何种事件发生,然后再做出相应的处理。

1.jpg

CKS32F4xx系列产品串口的配置

接下来我们讲解如何利用CKS32F4xx系列固件库来完成对串口的配置使用。首先标准库函数定义了一个串口初始化结构体USART_InitTypeDef,结构体成员用于设置串口的工作参数,并由外设初始化配置函数USART_Init()调用,从而完成对串口相应寄存器的配置,进一步达到完成对串口配置的目的。

typedef struct 
{ 
    uint32_t USART_BaudRate;   // 波特率 
    uint16_t USART_WordLength; // 字长 
    uint16_t USART_StopBits;    // 停止位 
    uint16_t USART_Parity;      // 校验位 
    uint16_t USART_Mode;      // USART 模式 
    uint16_t USART_HardwareFlowControl; // 硬件流控制 
} USART_InitTypeDef;

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值,并设置USART_BRR寄存器值。

2) USART_WordLength:数据帧字长,可选8位或9位。它设定USART_CR1 寄存器的M位的值。如果没有使能奇偶校验控制,一般使用8位数据帧长;如果使能了奇偶校验则一般设置为9位数据帧长。

#define USART_WordLength_8b                  ((uint16_t)0x0000)
#define USART_WordLength_9b                  ((uint16_t)0x1000)

3) USART_StopBits: 停止位设置,可选0.5个、1个、1.5个和 2个停止位,它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。

#define USART_StopBits_1                     ((uint16_t)0x0000)
#define USART_StopBits_0_5                   ((uint16_t)0x1000)
#define USART_StopBits_2                     ((uint16_t)0x2000)
#define USART_StopBits_1_5                   ((uint16_t)0x3000)

4) USART_Parity: 奇偶校验控制选择,可选USART_Parity_No(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定 USART_CR1寄存器的PCE位和PS位的值。

#define USART_Parity_No                      ((uint16_t)0x0000)
#define USART_Parity_Even                    ((uint16_t)0x0400)
#define USART_Parity_Odd                     ((uint16_t)0x0600)

5) USART_Mode: USART模式选择,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。

#define USART_Mode_Rx                        ((uint16_t)0x0004)
#define USART_Mode_Tx                        ((uint16_t)0x0008)

6) USART_HardwareFlowControl: 硬件流控制选择,只有在硬件流控制模式才有效,可选使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。

#define USART_HardwareFlowControl_None       ((uint16_t)0x0000)
#define USART_HardwareFlowControl_RTS        ((uint16_t)0x0100)
#define USART_HardwareFlowControl_CTS        ((uint16_t)0x0200)
#define USART_HardwareFlowControl_RTS_CTS    ((uint16_t)0x0300)

当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置,不使用时不需要设置。

typedef struct 
{ 
    uint16_t USART_Clock; // 时钟使能控制 
    uint16_t USART_CPOL; // 时钟极性 
    uint16_t USART_CPHA; // 时钟相位 
    uint16_t USART_LastBit; // 最尾位时钟脉冲 
} USART_ClockInitTypeDef;

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) USART_Clock: 同步模式下SCLK引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2寄存器的CLKEN位的值。

#define USART_Clock_Disable                  ((uint16_t)0x0000)
#define USART_Clock_Enable                   ((uint16_t)0x0800)

2) USART_CPOL: 同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定USART_CR2寄存器的CPOL位的值。

#define USART_CPOL_Low                       ((uint16_t)0x0000)
#define USART_CPOL_High                      ((uint16_t)0x0400)

3) USART_CPHA: 同步模式下SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。

#define USART_CPHA_1Edge                     ((uint16_t)0x0000)
#define USART_CPHA_2Edge                     ((uint16_t)0x0200)

4) USART_LastBit: 选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK引脚输出,可以是不输出脉冲(USART_LastBit_Disable)、输出脉冲 (USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。

#define USART_LastBit_Disable                ((uint16_t)0x0000)
#define USART_LastBit_Enable                 ((uint16_t)0x0100)

要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。

(1) void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)函数:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    assert_param(IS_USART_DATA(Data));   
    USARTx->DR = (Data & (uint16_t)0x01FF);
}

该函数的功能是向串口寄存器USART_DR写入一个数据,有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是待发送的数据,其值只要满足如下条件即可:

#define IS_USART_DATA(DATA) ((DATA) <= 0x1FF)

(2) uint16_t USART_ReceiveData(USART_TypeDef* USARTx)函数:

uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}

该函数的功能是从USART_DR寄存器读取串口接收到的数据,只有一个入口参数,即选择是哪个串口。

(3) void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)函数:

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    assert_param(IS_FUNCTIONAL_STATE(NewState));  
    if (NewState != DISABLE)  
    {    
        USARTx->CR1 |= USART_CR1_UE;  
    }  
    else  
    {  
        USARTx->CR1 &= (uint16_t)~((uint16_t)USART_CR1_UE);  
    }
}

要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。

该函数的功能是使能串口。有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是使能或者不使能,其值为DISABLE或者ENABLE。

(4) void USART3_IRQHandler(void) 串口中断服务程序函数:

当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写我们相应的逻辑代码即可。

(5) FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)函数:

该函数的功能是读取串口的状态,第一个入口参数和上面的一样。这里重点讲解第二个入口参数,它是标示我们要查看串口的哪种状态,可选的值及其代表的意义如表格所示:

2.jpg

(6) ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)函数:

当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。经常我们在中断处理函数中,要判断该中断是哪种中断,这时候就会使用该函数。第二个入口参数可选的值及其代表的意义如表格所示:

3.jpg

串口接发通信实验

接下来我们根据上面讲解的串口通信的知识,实际编写一个软件程序实现串口的接发通信。代码实现的现象是在开发板一上电时会通过print函数发送一串字符串“start”给电脑,然后开发板进入中断接收等待状态。如果电脑有发送数据过来,开发板就会产生中断, 我们在中断服务函数里接收数据,并将接收到数据标志位置1,在主函数里对标志位经过判断之后再把数据返回发送给电脑。

1.编程要点

1) 使能RX和TX引脚GPIO时钟和USART3时钟;

2) 初始化GPIO,并将GPIO复用到USART3上;

3) 配置USART3参数;

4) 配置中断控制器并使能USART3接收中断;

5) 使能USART3;

6) 在USART3接收中断服务函数里接收数据并将接收到数据的标志位置1。

2.代码分析

代码清单1:USART3初始化配置

其初始化串口的过程和我们前面讲解的编程要点中的过程是一致的。因为我们使用到了串口的中断接收,因此需要开启串口3的NVIC中断并对其进行配置。

void uart_init(u32 bound)
{    
    GPIO_InitTypeDef GPIO_InitStructure;  
    USART_InitTypeDef USART_InitStructure;  
    NVIC_InitTypeDef NVIC_InitStructure;
    //使能RX和TX引脚GPIO时钟  
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    //使能USART3时钟  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);  
    //初始化GPIO,并将GPIO复用到USART3上  
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3);   
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3);   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;   
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;   
    GPIO_Init(GPIOB,&GPIO_InitStructure);    
    //配置USART3参数  
    USART_InitStructure.USART_BaudRate = bound;    //波特率设置  
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;   
    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(USART3, &USART_InitStructure); 
    //使能USART3    
    USART_Cmd(USART3, ENABLE); 
    //配置中断控制器并使能USART3接收中断;  
    #if EN_USART3_RX    
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);   
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;   
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      
    NVIC_Init(&NVIC_InitStructure);  
    #endif  
}

代码清单2:USART3中断服务函数

当USART3有接收到数据时就会执行USART3_IRQHandler函数。然使用if 语句来判断是否是真的产生USART3数据接收这个中断事件,如果是真的就使用 USART数据读取函数USART_ReceiveData读取数据到指定存储区Res,并将自己定义的一个标志位Rxflag置1。

void USART3_IRQHandler(void)                {  if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)   {    Res =USART_ReceiveData(USART3);//(USART1->DR);      Rxflag=1;           } }

代码清单3:字符函数

//发送一个字符函数
static void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch )
{  
    USART_SendData(pUSARTx,ch);  
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);  
}
//发送指定长度字符的函数
void Usart_SendStr_length( USART_TypeDef * pUSARTx, uint8_t *str,uint32_t strlen )
{  
    unsigned int k=0;    
    do     
    {        
        Usart_SendByte( pUSARTx, *(str + k) );        
        k++;    
    } while(k < strlen);
}

Usart_SendByte函数用来在指定USART发送一个ASCLL码值字符,它有两个形参,第一个为USART,第二个为待发送的字符。它是通过调用库函数 USART_SendData来实现的,并且增加了等待发送完成功能。

Usart_SendString函数用来发送一个字符串,它实际是调用 Usart_SendByte函数发送每个字符,直到遇到空字符才停止发送。最后使用循环检测发送完成的事件标志来实现保证数据发送完成后才退出函数。

代码清单4:printf函数支持

#if 1
#pragma import(__use_no_semihosting)                             
struct __FILE 
{   
    int handle; 
}; 
FILE __stdout;         
void _sys_exit(int x) 
{   
    x = x; 
} 
int fputc(int ch, FILE *f)
{     
    while((USART3->SR&0X40)==0);//循环发送,直到发送完毕     
    USART3->DR = (u8) ch;        
    return ch;
}
#endif

这段代码是引入printf函数支持所必须的,加入这段代码加入之后便可以通过printf函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值。如果我们使用不同的串口,对这段代码的修改一般也只是用来改变 printf 函数针对的串口号,比如将上述代码中的USART3改成USART1即可。

代码清单5:主函数

u8 Res;u8 Rxflag;
u8 USART_RX_BUF[USART_REC_LEN];  
u8 usRxCount=0; int main(void)
{   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
    delay_init(168);       
    uart_init(115200);     
    printf("start\r\n");  
    while(1)  
    {    
        if(Rxflag)    
        {      
            if (usRxCount < sizeof(USART_RX_BUF))      
            {        
                USART_RX_BUF[usRxCount++] = Res;      
            }      
            else      
            {        
                usRxCount = 0;      
            }      
            /* 遇到换行字符,就把数据发送到串口助手*/    
            if (Res == 0x0A)  /* 换行字符 */    
            {         
                Usart_SendStr_length(USART3,USART_RX_BUF, usRxCount );        
                usRxCount = 0;      
            }      Rxflag=0;    
        }  
    }
}

首先我们调用NVIC_PriorityGroupConfig函数完成对NVIC的初始化,然后调用uart_init函数完成对串口的初始化,这里将串口波特率设置成可115200(位/秒)。接着利用printf函数发送一次“start”到串口调试助手。然后对Rxflag的值进行判断,当接收到了数据,即Rxflag的值为1时,对接收的数据长度进行判断,USART_REC_LEN是我们定义的接收最大字节数,这个值可以根据自己的需要进行修改。当接收的数据在最大字节数范围之内时,把接收到的数据赋值到数组USART_RX_BUF里,同时当接收到的数据为0x0A,即换行字符时,利用Usart_SendStr_length函数将接收到的数据发送出去。因此在利用串口调试助手向MCU发送数据时,要勾选“加回车换行符”。

在本程序中我们设置串口进入中断的方式为数据寄存器非空即进一次中断,因此每个字节的接收都会进一次中断,这会导致CPU的效率大大降低,因此在下一节我们将会讲解利用DMA的方式对串口的数据进行发送和接收。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 23