• STM32串口通信-简单版


    STM32串口通信-简单版

    参考原子串口通信的程序,编写一个STM32串口简单版的程序,巩固其原理以及要注意的点

    串口初始化

    初始化的部分都一样,但先不用中断,所以不用初始化NVIC

    /**
     * @name   USART1_Init
     * @brief  延时初始化
     * @param  None
     * @retval None
     */
    void USART1_Init(u32 bound)
    {
        GPIO_InitTypeDef GPIO_InitStruc;
        USART_InitTypeDef USART_InitStruc;
        //初始化GPIO和串口时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
    
        //初始化PA9,接到TX
        GPIO_InitStruc.GPIO_Mode = GPIO_Mode_AF_PP;    //设置为推挽输出
        GPIO_InitStruc.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruc.GPIO_Speed = GPIO_Speed_50MHz;
    
        GPIO_Init(GPIOA,&GPIO_InitStruc);
    
        //初始化PA10,接到RX
        GPIO_InitStruc.GPIO_Mode = GPIO_Mode_IN_FLOATING;    //设置为浮空输入
        GPIO_InitStruc.GPIO_Pin = GPIO_Pin_10;
    
        GPIO_Init(GPIOA,&GPIO_InitStruc);
    
        //初始化串口
        USART_InitStruc.USART_BaudRate = bound;         //波特率
        USART_InitStruc.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控制流
        USART_InitStruc.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;   //发送和接收模式
        USART_InitStruc.USART_Parity = USART_Parity_No; //无奇偶校验位
        USART_InitStruc.USART_StopBits = USART_StopBits_1;  //一位停止位
        USART_InitStruc.USART_WordLength = USART_WordLength_8b; //8位数据位
    
        USART_Init(USART1,&USART_InitStruc);
    
        //串口1使能
        USART_Cmd(USART1,ENABLE);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    发送一个字符

    初始化后,就可以在主函数中进行发送和接收操作了,这里是个最简单的发送操作,调用USART_SendData函数往串口1发送一个字符

    开发板上电后,就往串口发送字符H

    int main()
    {
    	u8 temp;
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    	USART_SendData(USART1,'H');
    	while(1)
    	{		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果连续调用USART_SendData函数发送数据,则会出现问题,如下面程序

    int main()
    {
    	u8 temp;
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    	USART_SendData(USART1,'H');
        USART_SendData(USART1,'E');
        USART_SendData(USART1,'L');
        USART_SendData(USART1,'K');
    	while(1)
    	{		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上电执行,串口助手只收到字符K,多个K是按了多次复位键产生的,刚上电时才只有一个K

    在这里插入图片描述

    这是因为程序执行太快,USART_SendData( )只是把数据放入到TDR寄存器中,数据还没来得及放入发送移位寄存器中,下一个数据就将DR寄存器的值给覆盖了,所以最后只有一个K字符没被覆盖,被转移到了移位寄存器中,再发送了出去
    在这里插入图片描述

    修改:

    因为串口的状态寄存器(USART_SR) 的TXE位可以判断数据是否被转移到移位寄存器,所以加上while循环判断,等待TXE位被置1,等数据已经全部转移到移位寄存器中,才将下一个数据装入TDR寄存器中

    int main()
    {
    	u8 temp;
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    	USART_SendData(USART1,'H');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'E');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'L');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'K');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	while(1)
    	{		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    串口正常发送了HELK四个字符

    在这里插入图片描述

    接收数据

    可以在主函数的while循环中接收上位机发来的数据,再将数据发送回去

    接收数据是调用USART_ReceiveData函数,接收标志位是USART_FLAG_RXNE

    int main()
    {
    	u8 temp;
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    	
    	USART_SendData(USART1,'H');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'E');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'L');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	USART_SendData(USART1,'K');
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	
    	while(1)
    	{
    		while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);	//等待RXNE标志位为1
    		temp = USART_ReceiveData(USART1);		//接收串口1的数据
    		USART_SendData(USART1,temp);			//再发送出去
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);		//等待发送完毕
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    程序结果:

    上电时单片机会发送‘HELK’四个字符,然后在串口助手上首先发送了abcd四个字符,再发送了”串口接收数据“四个汉字,串口1都接收到了,并原封不动地发送回串口助手上显示

    在这里插入图片描述

    发送字符串

    32发送字符串的操作与51的类似

    首先可以参考USART_SendData函数,写一个自己的发送字符函数,再写一个发送字符串的函数

    USART_SendByte函数体内容可以直接拷贝标准库函数USART_SendData的内容,然后在最后加上等待TXE标志位的while循环即可,因为发送字符串函数需要调用到这个USART_SendByte函数,所以避免在发送字符串时前一个字符还没有转移到移位寄存器,就被后一个字符给覆盖了,造成数据丢失的情况

    发送字符串原理:依次发送字符串中字符,每发送一个检查下TXE标志位,发送完全部字符以后,最后检查TC标志位。TC标志位是指数据完全从TX发送出去

    /**
     * @name   USART_SendByte
     * @brief  串口发送一个字节
     * @param  Data:要发送的数据
     * @retval None
     */
    void USART_SendByte(USART_TypeDef* USARTx, uint8_t Data)
    {
        /* Check the parameters */
        assert_param(IS_USART_ALL_PERIPH(USARTx));
        assert_param(IS_USART_DATA(Data)); 
        
        /* Transmit Data */
        USARTx->DR = (Data & (uint16_t)0x01FF);
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    }
    
    /**
     * @name   USART_SendString
     * @brief  串口1发送一个字符串
     * @param  str:要发送的数据
     * @retval None
     */
    void USART_SendString(USART_TypeDef* USARTx,char* str)
    {
        while(*str != '\0')
        {
            USART_SendByte(USARTx,*str++);
        }
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    写好函数后,就可以在主函数中调用进行字符串的发送

    int main()
    {
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    
    	USART_SendByte(USART1,'H');
    	USART_SendByte(USART1,'E');
    	
    	USART_SendString(USART1,"串口发送字符串\r\n");
    	
    	while(1)
    	{
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    程序结果:

    每按一次复位键就发送一次’HE’字符和后面的字符串

    在这里插入图片描述

    中断接收数据

    正因为原子的串口中断函数写有点复杂,不太好懂,但懂了之后发现其写法挺不错的,不过有时候只需要实现简单的功能,不想写这么复杂,所以这里编写一个简单的中断处理函数,将串口接收到的数据再发送回去

    首先要初始化NVIC,再使能串口中断,这些都在串口初始化函数中编写

    /**
     * @name   USART1_Init
     * @brief  延时初始化
     * @param  None
     * @retval None
     */
    void USART1_Init(u32 bound)
    {
        GPIO_InitTypeDef GPIO_InitStruc;
        USART_InitTypeDef USART_InitStruc;
        NVIC_InitTypeDef NVIC_InitStruc;
        //初始化GPIO和串口时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
    
        //初始化PA9,接到TX
        GPIO_InitStruc.GPIO_Mode = GPIO_Mode_AF_PP;    //设置为推挽输出
        GPIO_InitStruc.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruc.GPIO_Speed = GPIO_Speed_50MHz;
    
        GPIO_Init(GPIOA,&GPIO_InitStruc);
    
        //初始化PA10,接到RX
        GPIO_InitStruc.GPIO_Mode = GPIO_Mode_IN_FLOATING;    //设置为浮空输入
        GPIO_InitStruc.GPIO_Pin = GPIO_Pin_10;
    
        GPIO_Init(GPIOA,&GPIO_InitStruc);
    
        //NVCI初始化
        NVIC_InitStruc.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStruc.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStruc.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStruc.NVIC_IRQChannelSubPriority = 1;
    
        NVIC_Init(&NVIC_InitStruc);
    
        //初始化串口
        USART_InitStruc.USART_BaudRate = bound;         //波特率
        USART_InitStruc.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控制流
        USART_InitStruc.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;   //发送和接收模式
        USART_InitStruc.USART_Parity = USART_Parity_No; //无奇偶校验位
        USART_InitStruc.USART_StopBits = USART_StopBits_1;  //一位停止位
        USART_InitStruc.USART_WordLength = USART_WordLength_8b; //8位数据位
    
        USART_Init(USART1,&USART_InitStruc);
    
        //串口1使能
        USART_Cmd(USART1,ENABLE);
    
        //使能串口1接收中断
        USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    编写中断处理函数

    /**
     * @name   USART1_IRQHandler
     * @brief  中断处理函数
     * @param  None
     * @retval None
     */
    void USART1_IRQHandler(void)
    {
        u8 temp;
        //判断RXNE标志位是否为1,为1表示产生了接收中断
        if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
        {
            temp = USART_ReceiveData(USART1);   //对USART_DR的读操作可以将RXNE位清零
            USART_SendByte(USART1,temp);		//将接收到的数据再发送回去
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过查看状态寄存器(USART_SR) 的RXNE标志位说明可知,只要对USART_DR的读操作就可以将RXNE位清零,所以temp = USART_ReceiveData(USART1); 这一句刚好是将DR寄存器里的值读取到 temp 变量中,所以这一句执行过后RXNE就被清零了,不用手动清零,可以进行下一次数据接收

    在这里插入图片描述

    最后记得在主函数中初始化NVIC分组,while循环里就不用再接收数据了

    int main()
    {
    	//NVCI分组
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//设置为分组2
    	//延时初始化
    	delay_Init();
    	//串口初始化
    	USART1_Init(9600);
    
    	USART_SendByte(USART1,'H');
    	USART_SendByte(USART1,'E');
    	
    	USART_SendString(USART1,"串口发送字符串\r\n");
    	
    	while(1)
    	{
    		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    程序结果:
    在这里插入图片描述

    printf 重定向可以看这篇文章:串口通信printf函数重定向

  • 相关阅读:
    hive中使用iceberg表格式时锁表总结
    java毕业生设计原创网络文学管理系统计算机源码+系统+mysql+调试部署+lw
    【安装教程】vscode安装教程(超详细)
    【LeetCode-75】 颜色分类
    oracle11g安装图解
    笔面试学习——ARM体系与结构
    文盘Rust -- 把程序作为守护进程启动
    Kindle倒下,iReader接力
    【数字IC验证快速入门】7、验证岗位中必备的数字电路基础知识(含常见面试题)
    React——ES6语法补充
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/127336096