从零开始制作STM32F103RCT6小车(四)

7 篇文章 15 订阅

前言:

        本篇将使用串口与电脑进行通信,同时也会捎带着提一下串口的部分知识,后续会将树莓派与STM32建立通信

浅谈STM32串口:

        STM32F103RCT6芯片上拥有3个USART外设,(分别是UASRT1、UASRT2、UASRT3)以及两个UART外设(UART4、UART5)。

        USART即通用同步异步收发器,它能够灵活地与外部设备进行全双工数据交换它支持同步单向通信和半双工单线通信;还支 LIN(域互连网络)、智能卡协议与 IrDA(红外线数据协会) SIRENDEC 规范,以及调制解调器操作 (CTS/RTS)。并且可以使用 DMA 可实现高速数据通信。USART在STM32中应用最多的是printf输出调试信息,这里我会给大家讲解一下如何使用printf向串口助手打印信息

下面这段代码是对fputc的重定向,以便我们使用printf函数,我这里是使用UASRT2作为输出的,大家如果用其他串口可以更改一下串口号

#include "stdio.h"
int fputc(int ch, FILE *f)
{
	USART_SendData(USART2, (uint8_t) ch);
	
	while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET){}
	
  return (ch);
}

int fgetc (FILE *f)  
{
	while(USART_GetFlagStatus(USART2,USART_FLAG_RXNE) == RESET);
	return (int)USART_ReceiveData(USART2);
}

        此外,我们还需要调用一下专用的库,点击魔法棒,打开Target界面,选中箭头所指内容,最后点击OK就可以了

 之后,我们就可以在程序中使用printf了

        而UART即通用异步收发器,它是在USART基础上裁剪掉了同步通信功能,同步和异步主要看其时钟是否需要对外提供。

正片开始:

        配置串口的一般步骤如下所示

1、使能对应管脚时钟和串口时钟,这里我使用的PA2 PA3 USART2

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

        RCC_APB1PeriphClockCmd(RCC_APB2Periph_USART2,ENABLE);

2、设置GPIO工作模式,这里要注意,Tx要设置为GPIO_Mode_AF_PP,Rx要设置为GPIO_Mode_IN_FLOATING,不过Rx不需要配置管脚速度

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//TX

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//RX

3、初始化串口参数

        USART_InitStructure.USART_BaudRate = Baudrate;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_InitStructure.USART_Parity = USART_Parity_No;                 
        USART_InitStructure.USART_StopBits = USART_StopBits_1;        
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;

        USART_Init(USART2, &USART_InitStructure);

4、使能串口

        USART_Cmd(USART2, ENABLE);

5、设置串口中断类型并使能

        USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

6、设置串口中断优先级,使能串口中断通道

        这一点一开始我给忘了,结果就没能让串口进入中断,我这里是随便配置的,大家可以根据自己的需要进行配置

        NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
        NVIC_Init(&NVIC_InitStructure);

7、编写串口中断服务函数

串口中断中常见的几种函数:

        这里我给大家罗列一下几种常见的标志位,简单说一下这些标志位的功能,防止大家混淆

1、USART_FLAG_TXE该标志位是发送数据寄存器空标志位。发送数据寄存器里的数据被全部取完时,该寄存器是空的,那么该标志位就会被置1。通过这个标志位的值可以判断发送数据寄存器中的数据有没有完全被取走,当该寄存器是空的时候,可以提醒CPU继续往该寄存器里存入新的数据;

2、USART_FLAG_TC,发送移位寄存器里的每个字节通过TX脚一位一位发送出去之后,该标志位值就会被置1。通过这个标志位的值可以判断发送移位寄存器里的数据有没有被全部发送出去;若使用 USART_FLAG_TC来进行判断数据是否发送完成,注意需使能TC中断 USART_ITConfig(USART1, USART_IT_TC, ENABLE);

3、USART_IT_IDLE是串口收到一帧数据后,也可以叫做一包数据,才会产生置位。这里要想清除这个中断的话,必须要先读取SR寄存器,再读取DR寄存器,

USART2->SR; //先读SR寄存器
USART2->DR; //再读DR寄存器

4、USART_IT_RXNE当接收到1个字节,会产生USART_IT_RXNE中断,这里要想清除这个中断的话,要使用这个函数USART_ClearITPendingBit(USART2, USART_IT_RXNE);

串口中断中常见的几种问题:

        1、数据传输的过程中出现乱码,如果是一开始出现乱码,那可能是波特率没有选对的问题,如果是过程中出现乱码,我们这时候就要考虑到我们定义的数组长度不够的情况,也有可能是中断被打断,所以这里对于中断优先级的要求是比较高的。

        2、程序卡在中断服务函数中无法跳出,这时候就要考虑到是不是USART_FLAG_ORE被置位了,如果是的话,那么就要先对SR寄存器进行操作,再对DR寄存器进行操作,这时候也可以用

USART_ClearFlag(USART1,USART_FLAG_ORE);//清除ORE标志位
USART_ReceiveData(USART1);               //抛弃接收到的数据

        3、如果出现数据漏发现象,这时候可能是USART_DR寄存器里的TDR寄存器还没有将内容发给移位寄存器,还没发送出去,新的数据就被重新传入移位寄存器了。

数据处理所用到的函数->sscanf:

        为了方便处理串口助手传回来的数据,我这里调用了stdio.h下的sscanf函数,它的返回值是匹配成功的次数,根据返回值我们可以检验一下是否提取成功,只不过我这里是用其来提取字符串中所包含的数字。其用法大致跟printf差不太多,我这里也不再赘述

if(sscanf((const char *)rxData,"r:%dl:%d",&right_param,&lift_param) == 2)

代码部分:

#include "stm32f10x.h"
#include "UART.h"
#include "stdio.h"

// PA2    ->  UART2_Tx
// PA3    ->  UART2_Rx

uint8_t flag_full=0;
uint8_t rxData[RXDATA_SIZE];

unsigned int lift_param = 0;		//设置为全局变量
unsigned int right_param = 0;		


void UART2_Init(unsigned int Baudrate)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//TX
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//RX
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate = Baudrate;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//关闭            
    //硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;			 //无校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;		 //1位停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //8个数据位
	
	USART_Init(USART2, &USART_InitStructure);
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);	//允许接收中断
  USART_Cmd(USART2, ENABLE);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
	NVIC_Init(&NVIC_InitStructure);	
	
}

void USART_SendByte(USART_TypeDef * pUSARTx,uint8_t date)
{
	USART_SendData(pUSARTx,date);
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
}

void USART_SendString(USART_TypeDef * pUSARTx,char *str)
{
	while( *str!='\0')
	{
		USART_SendByte(pUSARTx,*str++);
	}
	
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}

void USART2_IRQHandler()
{
	static unsigned int i=0;
	uint8_t Res;
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  
	{ 
		USART_ClearITPendingBit(USART2,USART_IT_RXNE);
		Res =USART_ReceiveData(USART2);	

		if(!flag_full)
		{
			rxData[i]=Res;
			i++;
			if(rxData[i-1]=='\n'||i>RXDATA_SIZE)
			{
			  rxData[i] = 0;
				flag_full=1;	//这个标志位保证每次只能进一次中断
				i=0;
			}
		}else
		{
    }		
	}
}

void data_processing()
{
	if(flag_full == 1)
	{
		if(sscanf((const char *)rxData,"r:%dl:%d",&right_param,&lift_param) == 2)
		{
			flag_full = 0;//开始下一次读取
//			USART_SendString(USART2,"r:ok");
//			USART_SendString(USART2,"l:ok\r\n");
//			while(USART_GetFlagStatus(USART2,USART_FLAG_TC) != SET);
			printf("r:%04d\r\n",right_param);//%04d,左侧自动补零,这样在串口助手上好看些
			printf("l:%04d\r\n",lift_param);
		}
	}
}

//重定向fputc方便使用printf向串口助手打印数据
int fputc(int ch, FILE *f)
{
	USART_SendData(USART2, (uint8_t) ch);
	
	while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET){}
	
  return (ch);
}

int fgetc (FILE *f)  
{
	while(USART_GetFlagStatus(USART2,USART_FLAG_RXNE) == RESET);
	return (int)USART_ReceiveData(USART2);
}

uart.h

#ifndef  __UART_H
#define  __UART_H

#include "stm32f10x.h"
#include "stdio.h"
#define RXDATA_SIZE 500

void UART2_Init(unsigned int Baudrate);
void USART_SendByte(USART_TypeDef * pUSARTx,uint8_t date);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void data_processing(void);
int fputc(int ch, FILE *f);
int fgetc (FILE *f);

#endif

main.c

#include "stm32f10x.h"
#include "led.h"
#include "sysclock.h"
#include "motor.h"
#include "UART.h"


int main()
{
	LED_init();
	Motor1_Init();
	Motor2_Init();
	Motor3_Init();
	Motor4_Init();
	USART2_Init(115200);
	GPIO_WriteBit(GPIOC, GPIO_Pin_12, Bit_RESET);
	USART_SendString(USART2,"hello\r\n");
	while(1)
	{
//		Motor1_forward(1999);
//		Motor2_forward(7999);
//		Motor3_forward(1999);
//		Motor4_forward(1999);
		data_processing();
	}
}

成果展示:

        这里呢,我用串口助手向STM32F103RCT6发送数据,数据格式位r:xxxxl:xxxx,回馈的数据格式为r:xxxx\r\n l:xxxx\r\n

 后续将会以树莓派4B作为上位机向STM32下位机发送数据,数据格式仍为上述格式。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: STM32F103RCT6是一款基于ARM Cortex-M3内核的32位微控制器。工程模版是为了方便开发者在使用该芯片进行项目开发时,可快速搭建项目框架而设计的。 这个工程模版通常包含以下组成部分: 1. 硬件初始化:包括时钟设置、外部中断配置、GPIO初始化等。这些步骤是为了确保芯片的硬件资源能够正常工作。 2. 中断处理:该模版通常会包含一些中断处理函数,用于响应外部事件,如按键中断、定时器中断等。开发者可以根据自己的需求对中断进行配置和定制。 3. 外设配置:该模版还会包含对一些常用的外设进行配置,如串口、I2C、SPI等。这样开发者在使用这些外设时就不需要从头编写配置代码,可以直接在模版基础上进行开发调试。 4. 任务调度:在一些较为复杂的应用中,可能会使用到任务调度器来管理多个任务的执行。工程模版中可能会集成对任务调度的支持,以方便开发者进行多线程开发。 5. 库函数支持:该模版一般会集成一些常用的库函数,如延时函数、数学函数等。这些函数可以帮助开发者快速实现一些常用的功能,提高开发效率。 通过使用这个工程模版,开发者可以快速搭建项目框架,节省大量的开发时间。同时,模版的结构清晰,易于理解和维护。开发者可以在这个模版的基础上进行二次开发,添加自己的代码,实现特定的应用需求。 ### 回答2: stm32f103rct6是一款由STMicroelectronics公司生产的32位ARM Cortex-M3内核的微控制器。工程模版是指在进行stm32f103rct6开发时使用的初始项目结构和配置文件的预设。 stm32f103rct6工程模版通常包括以下内容: 1. 硬件配置:工程模版会包含针对stm32f103rct6芯片的引脚配置文件,以及时钟树配置文件。这些配置文件可以定义芯片上各个引脚的功能,并设置系统时钟。 2. 中断向量表:stm32f103rct6工程模版会预设好中断向量表的初始设置,包括各个中断向量的初始地址和中断服务函数的名称。开发者可以根据需要自定义中断服务函数。 3. 系统初始化代码:工程模版会包含一个系统初始化的代码文件,用于设置系统时钟、外设初始化、时钟配置和中断向量表的初始化等工作。 4. 示例代码:工程模版通常会提供一些示例代码,展示常见的外设配置和使用方法,如GPIO、UART、SPI等。开发者可以根据示例代码进行二次开发。 5. 编译和调试配置:工程模版会预设好编译器、链接器和调试器的配置文件,以便开发者可以直接编译、烧录和调试代码。 通过使用stm32f103rct6工程模版,开发者可以快速开始stm32f103rct6的开发,并且避免了一些基础的配置和初始化的麻烦。开发者只需要在工程模版的基础上添加自己的代码,即可进行stm32f103rct6的功能扩展和应用开发。 ### 回答3: STM32F103RCT6是STMicroelectronics公司推出的一款32位ARM Cortex-M3内核的微控制器。工程模板是用于快速开发STM32F103RCT6项目的起点,包含了一些基本的配置和代码结构。 首先,工程模板通常包括了开发环境的配置,例如选择合适的编译器和调试工具。例如,你可以选择使用Keil MDK或者IAR Embedded Workbench作为开发工具。 接下来,工程模板会包含了一些基本的设置,例如时钟配置、引脚配置和外设设置等。时钟配置是非常重要的,因为它决定了微控制器系统的主频和时钟源。引脚配置涉及到将外设连接到正确的引脚上,以确保能够正常操作外设。外设设置是针对具体的项目需求进行的配置,例如串口、I2C、SPI等外设的初始化设置。 此外,工程模板还会包含一些基本的代码结构。其中,启动代码是必不可少的,它负责初始化系统时钟、堆栈设置等,确保正确启动微控制器。还有一些驱动代码,用于操作外设,例如GPIO驱动、SPI驱动等。另外,工程模板还会包含一些示例代码和函数库,供开发者参考。 使用STM32F103RCT6工程模板可以大大加快开发的进度,减少开发的工作量。开发者只需要在基础模板的基础上进行修改和扩展,即可进行自己的应用开发。同时,工程模板也提供了一些常用的函数和代码示例,方便开发者进行调试和测试。 总之,STM32F103RCT6工程模板为开发者提供了一个快速起步的基础,包含了基本的配置和代码结构,方便开发者进行STM32F103RCT6项目的开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值