文章目录
详细资料见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 蓝牙串口模块
无线类模块那么多,为什么用蓝牙串口模这个呢。主要考虑到如下几点:
- 波特率最高可以设置为230400,传输速度较快。
- 带有连接状态显示led灯,两个模块连接成功灯常亮。这个功能十分有用。
- 可以进行全双工通信。
- 价格在可接收范围内。
- 使用较为简单的串口进行通信。
- 可以连接手机(官方有个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下。当然实际使用时,官方提供的库函数有自动计算波特率分频系数的部分。
- 位 15:0 DIV(这 16 位定义了 USART 分频系数)
- 12.12.4控制寄存器1(USART_CTRL1) rendered
包含了对USART使能、中断使能、发送接收数据位长度等设置的位。详见图3.2.3.2
- 12.12.3波特比率寄存器(USART_BAUDR)
图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
用于设置优先级组的选择,具体设置见下图:
- Bits 10:8 PRIGROUP
图3.2.3.4-优先级组的选择
- 4.4.5 Application interrupt and reset control register (AIRCR)
-
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:串口停止位宽度
- 返回值 无
- 函数原型 void usart_init(usart_type* usart_x, uint32_t baud_rate, usart_data_bit_num_type
-
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)
- 函数原型 void usart_interrupt_enable(usart_type* usart_x, uint32_t usart_int, confirm_state
-
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:响应优先级设定
- 函数原型 void nvic_irq_enable(IRQn_Type irqn, uint32_t preempt_priority, uint32_t
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”清零。