3.2 Hello World——UART,启动!


详细资料见Q群:531905626

对该项目感兴趣的同志,想要一起深入开发拓展可以联系本人邮箱:2560198277@qq.com。限于本人精力,在PCB设计、3D建模、嵌入式开发方面都有较大的需求。

书籍总目录:http://t.csdnimg.cn/YDe8m


3.2 Hello World——UART,启动!

3.2.1 硬件设计

本小节用到的硬件资源有:
(1)AT32飞控USART3
  USART3_TX_PC4
  USART3_RX_PC5
(2)一对汇承蓝牙串口模块(淘宝链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.df27523cNEFkAp&id=563379037192&ns=1&abbucket=19#detail)

在这里插入图片描述

图3.2.1.1 USART3 与 GH1.25接口


图3.2.1.2 蓝牙串口模块

无线类模块那么多,为什么用蓝牙串口模这个呢。主要考虑到如下几点:

  1. 波特率最高可以设置为230400,传输速度较快。
  2. 带有连接状态显示led灯,两个模块连接成功灯常亮。这个功能十分有用。
  3. 可以进行全双工通信。
  4. 价格在可接收范围内。
  5. 使用较为简单的串口进行通信。
  6. 可以连接手机(官方有个APP与微信公众号)

当然其也有传输距离有限(几十米)的缺点。其他常见的如无线串口模块传输距离虽然非常远,但却只能半双工通信(即不能同时接收与发送),这不利于程序调试,之后使用匿名上位远程调参等也可能出现问题。市面上也有全双工无线模块的(毕竟蓝牙本质上也是用无线电),不过没试过了。

同时汇承蓝牙模块可以使用“5、软件资料\(1)软件\HC-42蓝牙设置上位机”中的“HC-T串口助手V1.6(2022.09.20).exe”软件设置波特率、主从机等。

使用“HC-T串口助手”设置蓝牙模块。

……(待写)

3.2.2 下载验证

在具体讲解前,先下载例程,看一下具体效果。

进入“2、飞控例程\2_USART”,打开工程并编译。

按住BOOT按键,给飞控上电,进入Bootloader(同时进入USB的DFU模式)。编译例程,双击“Download_tool_ISP”文件夹下的“1_Download_by_usb.bat”,开始下载程序。

该例程的功能为:通过printf与普通串口发送数据(周期500ms),通过串口中断接收数据并返回。

此时接好飞控、电脑两端的蓝牙模块,当两个模块蓝灯常亮时表示连接成功,若使用汇承配套的串品模块,还可以看到串口模块上黄灯第500ms闪烁一次,表示正在接收数据。

在这里插入图片描述

图3.2.2.1 蓝牙模块连接

其后打开“5、软件资料\(1)软件\XCOM(正点原子推荐)”中的XCOM V2.3.exe软件,设置参数如下图,注意波特率230400,然后选中“打开串口”。

在这里插入图片描述

图3.2.2.2 XCOM参数设置
正常情况下,XCOM会定时接收到AT32飞控发来的信息,并且当上位机发送消息给飞控时,飞控会将数据回返。

3.2.3 UART、USART简介

(1)两者区别

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)与USART(Universal Synchronous /Asynchronous Receiver / Transmitter,异步同步通信接口),从名字上看只是USART多了一个“同步”的功能。那什么是异步、同步通信呢?

简言之就是看有没有时钟引脚,UART通信过程中第个高低电平的周期是固定的,如果高低电平宽度发生改变(相当于改变波特率),则通信可能会出错。

而如果用SPI(为同步通信)通信,双方在同一个时钟信号的控制下(时钟引脚)进行数据的接收和发送,哪怕信号引脚高低电平的宽度来回变化,只要与时钟引脚对应,就不会因此通信出错。

USART便是可以设置为与SPI类似的同步通信。“6、AT32官方资料\(1)教程与文档\(2)AT32F435 参考手册(寄存器).pdf”的“2.4 外设地址映射”中可以看到,AT32F435VMT7的USART有四个:
USART1、USART2、USART3、USART6
UART也有四个:
UART4、UART5、UART7、UART8

同时各USART除RX、TX引脚外也有对应的CK(时钟)引脚,而UART没有。
在这里插入图片描述

图3.2.3.1

但实际开发过程中一般还是使用较为简单的UART,但为了方便有时将USART、UART都称为USART

(2)USART相关的寄存器

  • USART相关寄存器
    • 12.12.3波特比率寄存器(USART_BAUDR)
      • 位 15:0 DIV(这 16 位定义了 USART 分频系数)
        该寄存器只是设置USART时钟的分频系数,想要设置好准确的波特率,还要根据USART的时钟源频率进行计算。而USART的时钟源就是其所挂载的总线的时钟,其中除了USART1、6挂载在APB2总线下外,其他都挂载在APB1下。当然实际使用时,官方提供的库函数有自动计算波特率分频系数的部分。
    • 12.12.4控制寄存器1(USART_CTRL1) rendered
      包含了对USART使能、中断使能、发送接收数据位长度等设置的位。详见图3.2.3.2


图3.2.3.2

  • 12.12.1状态寄存器(USART_STS)
    包含了接收、发送等状态的位,详见图3.2.3.3

图3.2.3.3

  • 12.12.2数据寄存器(USART_DT)
    只用了低8位。该寄存器包含读和写的功能。当奇偶校验位使能,发送操作时,写到 MSB(最高有效位) 的值会被校验位取代。接收操作时,读到的 MSB 位是接收到的校验位。(所以一般不设置校验位,以留出数据位的空间?)

  • 中断相关寄存器
    NVIC并不属于AT32的外设,而是Cotex-M4内核的一部分,因此在芯片的参考手册中很难找到相关信息。而CM4内核寄存器的详细信息可以参考“3、参考资料\STM32F3与F4系列Cortex M4内核编程手册.pdf”

    • 4.4 System control block (SCB)

      • 4.4.5 Application interrupt and reset control register (AIRCR)
        即 应用程序中断和复位控制寄存器,是属于System control block (SCB)即系统控制块的一个寄存器。
        • Bits 10:8 PRIGROUP
          用于设置优先级组的选择,具体设置见下图:
          在这里插入图片描述

      图3.2.3.4-优先级组的选择

    • 4.3 Nested vectored interrupt controller (NVIC)
      即 嵌套矢量中断控制器,CM4内核支持 256 个中断,各中断的中断函数在汇编startup_at32f435_437.s文件中有定义(即中断向量表)。同时在“2、飞例程\2_USART\lib\CMSIS\Core\Include”中的“core_cm4.h”中列出了NVIC等相关内核寄存器,如下图:
      在这里插入图片描述

    图3.2.3.5

    • ISER[8]:ISER
      全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。CM4内核用 8 个 32 位寄存器来控制最多256个中断的使能。
    • ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。
    • ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。
    • ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。
    • IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。
    • IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。
      而每个可屏蔽中断占用的 8bit 并没有全部使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的PRIGROUP位(中断分组)设置来决定。

(3)USART相关库函数

  • 5.24.2 函数 usart_init

    • 函数原型 void usart_init(usart_type* usart_x, uint32_t baud_rate, usart_data_bit_num_type
      data_bit, usart_stop_bit_num_type stop_bit);
    • 功能描述 波特率、数据位和停止位等进行设定
    • 输入参数 1 usart_x:指定的串口外设,如:USART1、USART2、USART3…
    • 输入参数 2 baud_rate:串口使用的通讯波特率
    • 输入参数 3 data_bit:串口数据位宽度
    • 输入参数 4 stop_bit:串口停止位宽度
    • 返回值 无
  • 5.23.5 函数 usart_transmitter_enable

    • 函数原型 void usart_transmitter_enable(usart_type* usart_x, confirm_state new_state);
    • 功能描述 外设发送使能设置
    • 输入参数 1 usart_x:指定的串口外设,如:USART1、USART2、USART3…
    • 输入参数 2 new_state:设置的新状态,使能(TRUE)失能(FALSE)
  • 5.24.6 函数 usart_receiver_enable

    • 函数原型 void usart_receiver_enable(usart_type* usart_x, confirm_state new_state);
    • 功能描述 外设接收使能设置
    • 输入参数 1 usart_x:指定的串口外设,如:USART1、USART2、USART3…
    • 输入参数 2 new_state:设置的新状态,使能(TRUE)失能(FALSE)
  • 5.24.9 函数 usart_interrupt_enable

    • 函数原型 void usart_interrupt_enable(usart_type* usart_x, uint32_t usart_int, confirm_state
      new_state);
    • 功能描述 中断使能设置
    • 输入参数 1 usart_x:指定的串口外设,如:USART1、USART2、USART3…
    • 输入参数 2 usart_int:指定的中断类型(接收、发送等)
    • 输入参数 3 new_state:设置的新状态,使能(TRUE)失能(FALSE)
  • 5.24.4 函数 usart_enable

    • 函数原型 void usart_enable(usart_type* usart_x, confirm_state new_state);
    • 功能描述 外设使能设置
    • 输入参数 1 usart_x:指定的串口外设,如:USART1、USART2、USART3…
    • 输入参数 2 new_state:设置的新状态,使能(TRUE)失能(FALSE)
  • 5.16.4 函数 nvic_priority_group_config

    • 函数原型 void nvic_priority_group_config(nvic_priority_group_type priority_group)
    • 功能描述 NVIC 中断优先级分组配置
    • 输入参数 priority_group:中断优先级分组选择,用于选择中断优先级分组
      NVIC_PRIORITY_GROUP_0:优先级组 0(0 位用于抢占优先级,4 位用于响应优先级)
      NVIC_PRIORITY_GROUP_1:优先级组 1(1 位用于抢占优先级,3 位用于响应优先级)
      NVIC_PRIORITY_GROUP_2:优先级组 2(2 位用于抢占优先级,2 位用于响应优先级)
      NVIC_PRIORITY_GROUP_3:优先级组 3(3 位用于抢占优先级,1 位用于响应优先级)
      NVIC_PRIORITY_GROUP_4:优先级组 4(4 位用于抢占优先级,0 位用于响应优先级)
  • 5.16.2 函数 nvic_irq_enable

    • 函数原型 void nvic_irq_enable(IRQn_Type irqn, uint32_t preempt_priority, uint32_t
      sub_priority)
    • 功能描述 NVIC 中断使能及优先级配置
    • 输入参数 1 irqn:中断向量选,是一个枚举体变量
    • 输入参数 2 preempt_priority:抢占优先级设定
    • 输入参数 3 sub_priority:响应优先级设定

3.2.4 软件设计

(1)USART初始化
#include "qy_common_header.h"

//开始任务设置
static TaskHandle_t START_handler;						    //任务句柄
static void START_task_function(void *pvParameters);		//任务函数


/**
  * @brief  main function.
  * @param  none
  * @retval none
  */
  
int main(void)
{	
	/***************************************** 系统时钟初始化 *****************************************/
	system_clock_config();
	
	
	xTaskCreate(START_task_function, "START_task", 300, NULL, 2, &START_handler);	/*创建起始任务*/
					
	/* start scheduler */
	vTaskStartScheduler();			//初始化滴答定时器等
}


void START_task_function(void *pvParameters)		//开始任务
{
	
	/****************************************** 进入临界区,原子操作 ***************************************/
	taskENTER_CRITICAL();
	
	/****************************************** 地面站串口初始化 *****************************************/
	printfSerialInit();
	ANO_init_usart(230400);
	
	/****************************************** LED初始化 ************************************************/
	My_LED_init();
	
	
#ifdef QY_BY_CORE_BOARD			//AT32核心板
	
#else							//AT32飞控
	
#endif
	
	
	/* 创建其他任务 */
	qy_tasks_creat();	

	
	vTaskDelete(START_handler);		//删除开始任务
	/* 退出临界区 */
	taskEXIT_CRITICAL();
}

main函数与开始任务创建不再累述。

行34~35:
为串口的初始化,含printf与ANO(匿名上位机)两个串口初始化函数。由于这里printf与ANO用的都是USART3,所以后者只是比前者多了串口接收与接收中断方面的内容。

(1.1)printfSerialInit()函数

定义在“qy_drivers\usart.c”中

#include "qy_common_header.h"
#include "qy_common_config.h"


/**************************printf函数重定义****************************************/
struct __FILE
{
	int handle;
};

FILE __stdout;

void _sys_exit(int x)
{
	x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{
	ch = ch;
}
	
int fputc(int ch, FILE *f)		//重定义printf
{
  while(usart_flag_get(PRINT_UART, USART_TDBE_FLAG) == RESET);
  usart_data_transmit(PRINT_UART, ch);
  return ch;
}


void printfSerialInit(void)
{
		gpio_init_type gpio_init_struct;

	#if defined (__GNUC__) && !defined (__clang__)
	setvbuf(stdout, NULL, _IONBF, 0);
	#endif

	/* enable the uart and gpio clock */
	crm_periph_clock_enable(PRINT_UART_CRM_CLK, TRUE);
	crm_periph_clock_enable(PRINT_UART_TX_GPIO_CRM_CLK, TRUE);

	gpio_default_para_init(&gpio_init_struct);

	/* configure the uart tx pin */
	gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
	gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
	gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
	gpio_init_struct.gpio_pins = PRINT_UART_TX_PIN;
	gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
	gpio_init(PRINT_UART_TX_GPIO, &gpio_init_struct);

	gpio_pin_mux_config(PRINT_UART_TX_GPIO, PRINT_UART_TX_PIN_SOURCE, PRINT_UART_TX_PIN_MUX_NUM);

	/* configure uart param */
	usart_init(PRINT_UART, PRINT_UART_BAUD, USART_DATA_8BITS, USART_STOP_1_BIT);
	usart_transmitter_enable(PRINT_UART, TRUE);
	usart_enable(PRINT_UART, TRUE);
}


行31~59:
可以看到只是简单的外设初始化,依次进行了时钟使能、GPIO初始化、USART初始化。

行5~28:
至于printf函数与自定义的串口是怎么联系上的,这里参考了AT32官方给出的例程。主要是23~28行将fputc()函数进行了重定义,使之的作用就是通过设定的串口发送一个字节数据。

(1.2)ANO_init_usart()函数

定义在“qy_math\ANO.c”中

void ANO_init_usart(uint32_t baudrate)
{
	gpio_init_type gpio_init_struct;

	/* enable the uart and gpio clock */
	crm_periph_clock_enable(ANO_UART_CRM_CLK, TRUE);
	crm_periph_clock_enable(ANO_UART_TX_GPIO_CRM_CLK, TRUE);
	crm_periph_clock_enable(ANO_UART_RX_GPIO_CRM_CLK, TRUE);

	gpio_default_para_init(&gpio_init_struct);

	/* configure the uart tx and rx pin */
	gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
	gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
	gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
	gpio_init_struct.gpio_pins = ANO_UART_TX_PIN;
	gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
	gpio_init(ANO_UART_TX_GPIO, &gpio_init_struct);

	gpio_pin_mux_config(ANO_UART_TX_GPIO, ANO_UART_TX_PIN_SOURCE, ANO_UART_TX_PIN_MUX_NUM);
	
	gpio_init_struct.gpio_pins = ANO_UART_RX_PIN;
	gpio_init(ANO_UART_RX_GPIO, &gpio_init_struct);

	gpio_pin_mux_config(ANO_UART_RX_GPIO, ANO_UART_RX_PIN_SOURCE, ANO_UART_RX_PIN_MUX_NUM);
	
	
	/* config usart nvic interrupt */
	nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);		//优先级组 4(4 位用于抢占优先级(0~15),0 位用于响应优先级)
	nvic_irq_enable(ANO_UART_IRQn, ANO_IRQ_Priority, 0);	//NVIC 中断使能及优先级配置(抢占优先级, 响应优先级)
	

	/* configure uart param */
	usart_init(ANO_UART, baudrate, USART_DATA_8BITS, USART_STOP_1_BIT);
	usart_transmitter_enable(ANO_UART, TRUE);
	usart_receiver_enable(ANO_UART, TRUE);
	
	usart_interrupt_enable(ANO_UART, USART_RDBF_INT, TRUE);		//使能接收中断 
	usart_enable(ANO_UART, TRUE);								//使能串口
}

除了时钟、GPIO、USART发送相关的设置外,

行29~30:
对NVIC进行设置,这里为了方便设置优先级分组为NVIC_PRIORITY_GROUP_4,即 4 位用于抢占优先级(0~15),0 位用于响应优先级。宏定义ANO_IRQ_Priority代表了设置的串口接收的优先级。
谈到优先级,你可能会想到FreeRTOS时钟也就是Systick的中断。“user/FreeRTOSConfig.h”中有如下定义:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5                       //系统可管理的最高中断优先级

这个宏定义与FreeRTOS的中断有关,FreeRTOS只能打断比该宏优先级更低的中断(猜测便是Systick中断的优先级)。这里设置为5
以此为对照,我们心里要对之后使用的每个中断的重要性心里有个顺序,并依次给予不同的优先级。如之后使用的航模遥控器接收机的中断就很重要,为了防止FreeRTOS打断,要给低于5的优先级。而这里使用的ANO串口接收中断,只与设置与显示有关,优先级可给低些,在“ANO.h”中设置了宏“ANO_IRQ_Priority”:

#define ANO_IRQ_Priority			   7					    //中断优先级

行:34
设置USART的具体参数,波特率230400,8个数据位,1个停止位,其它参数定义在ANO.h中。

(2)USART调用
void qy_tasks_creat(void)
{
	xTaskCreate(ANO_task_function, 	      "ANO_task",		 250, NULL, 15, &ANO_handler	   );	/*创建ANO任务	   堆栈250 优先级5 */
	xTaskCreate(State_show_task_function, "State_show_task", 200, NULL, 2,  &State_show_handler);	/*创建状态显示任务 堆栈200 优先级2 */
	
	xTaskCreate(Test_task_function, 	  "Test_task",		 250, NULL, 15, &Test_handler	   );	/*创建测试任务	   堆栈250 优先级15*/
}

任务创建函数中新加入了"ANO_task"匿名上位机任务,串口相关的操作都在其对应的ANO_task_function函数中执行。
ANO_task_function定义在“qy_tasks\qy_ANO_task.c”中,如下:

/**
  **************************************************************************
  * qy_ANO_task
  * @brief    ANO匿名上位机文件
  * @writer   清月默默
  * @time	  2023.12.6
  **************************************************************************
  */

#include "qy_common_header.h"

TaskHandle_t ANO_handler;						//任务句柄
void ANO_task_function(void *pvParameters);		//任务函数

char USART_RX_BUF[ANO_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记



/* ANO任务函数 */
void ANO_task_function(void *pvParameters)
{
	char tx_buf[50] = {0};
	
	while(1)
	{	
		if(USART_RX_STA&0x8000)		//接收完成
		{					   
			printf("\r\n您发送的消息为:\r\n\r\n");
			ANO_tx_buffer(USART_RX_BUF, sizeof(USART_RX_BUF));
			printf("\r\n\r\n");		//插入换行
			USART_RX_STA=0;
		}
		else
		{
			printf("Printf of USART_3 run successfully. \r\n");
			sprintf(tx_buf, "ANO USART_3 run successfully. \r\n");
			ANO_tx_buffer(tx_buf, sizeof(tx_buf));
			printf("\r\n\r\n");
		}
		
		vTaskDelay(500);
	}
}


/**
  * @brief  ANO串口中断回调函数
  * @param  无
  * @retval 无
  */
void ANO_IRQ_Function(void)			
{
	u8 res;
	
	if(ANO_UART->sts_bit.rdbf)		/********** 为串口接收中断	 **********/	
	{
		res = ANO_UART->dt;		//得到 数据寄存器(USART_DT) 的值
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}else //还没收到0X0D
			{	
				if(res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
					USART_RX_STA++;
					if(USART_RX_STA>(ANO_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}  		 			
	}
	
	
}

(2.1)串口发送

行39~44:
使用printf与普通串口两种方式发送数据,这里还用到了sprintf()函数,其功能与printf()类似,只是将转换所得字符串保存在了一个char*类型的指针中。

(2.2)串口接收

行51~84:
这是ANO串口接收中断的中断回调函数,基中ANO_IRQ_Function是一个宏,定义在“ANO.h”中:

#define ANO_IRQ_Function			   USART3_IRQHandler		//串口中断函数

该函数将接收到的数据依次存储进行15所定义的数组中。并在接收到‘\r’与‘\n’后判断完成数据接收。
同时为判断串口接收状态,另在行20定义了一个变量USART_RX_STA,保存有接收数据的个数。

行31~37:
判断是否接收到一段数据,有的话将接收到是的数据返回上位机,并将“USART_RX_STA”清零。

(2.1)串口发送

行39~44:
使用printf与普通串口两种方式发送数据,这里还用到了sprintf()函数,其功能与printf()类似,只是将转换所得字符串保存在了一个char*类型的指针中。

(2.2)串口接收

行51~84:
这是ANO串口接收中断的中断回调函数,基中ANO_IRQ_Function是一个宏,定义在“ANO.h”中:

#define ANO_IRQ_Function			   USART3_IRQHandler		//串口中断函数

该函数将接收到的数据依次存储进行15所定义的数组中。并在接收到‘\r’与‘\n’后判断完成数据接收。
同时为判断串口接收状态,另在行20定义了一个变量USART_RX_STA,保存有接收数据的个数。

行31~37:
判断是否接收到一段数据,有的话将接收到是的数据返回上位机,并将“USART_RX_STA”清零。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值