详细资料见Q群:531905626
对该项目感兴趣的同志,想要一起深入开发拓展可以联系本人邮箱:2560198277@qq.com。限于本人精力,在PCB设计、3D建模、嵌入式开发方面都有较大的需求。
书籍总目录:http://t.csdnimg.cn/YDe8m
第三章 最小系统
运用作品驱动,本章的目标是构建一个最小的飞行控制系统。该系统可以满足基本的飞行控制功能并具有易于向上扩展的工程结构,具有基本的飞行控制使用价值。简言之,够你玩航模了。
3.1 Hi World——LED,启动!
本节假设你
-
- 学习过stm32基础的GPIO驱动
-
- 学习过FreeRTOS基本的任务、延时等API函数
3.1.1 AT32 IO 简介
(1)工程结构简介
进入本节的例程“2、飞控例程\1_LED”,双击打开工程文件。
图3.1.1.1
如图3.1.1.1,工程中各文件夹的作用为:
-
user
用户文件夹。- 图中1是main.c文件,含有C语言的入口main函数
- 图中2是FreeRTOS的设置头文件,用于设置FreeRTOS的时钟频率等。
- 图中3是AT32的汇编启动文件,芯片上电或复位后最开始执行的还是main函数里的内容,而是先执行汇编文件进行中断向量表等的初始化。
-
qy_tasks
给本书开发的飞控软件工程一个名字,“清月飞控,英文名“Qyflight”。此文件夹用于放置飞控任务相关的源码。- 图中4为Qyflight的全局设置头文件,内含引脚分配……等定义。
- 图中5为Qyflight任务管理文件。
- 图中6为作为示例的一个任务,每个任务单独放于一个文件中,便于移植。
-
qy_drivers
此文件夹中放置飞控底层驱动与外部设备驱动的源码。 -
qy_math
此文件夹中放置飞控算法相关的源码 -
lib_freertos
此文件夹中放置FreeRTOS相关的源码 -
lib_AT32F43x
此文件夹中放置官方给的AT32F43x系列芯片驱动库源码。
(2) IO配置用到的库函数
这里只给出用到库函数的基本信息,具体细节参考“6、AT32官方资料\(1)教程与文档\(1)AT32F435_437固件库BSP&Pack应用指南.pdf”(以下简称固件库指南),以下也给出了各函数在固件库指南中的具体位置。
- 5.5.8 函数 crm_periph_clock_enable
- 函数原型 void crm_periph_clock_enable(crm_periph_clock_type value, confirm_state
new_state); - 功能描述 外设时钟使能设置
- 输入参数 1 value:指定的片上外设时钟类型
- 输入参数 2 new_state:新的时钟设置状态。开启(TRUE),关闭(FALSE)
- 函数原型 void crm_periph_clock_enable(crm_periph_clock_type value, confirm_state
- 5.14.3 函数 gpio_default_para_init
- 函数原型 void gpio_default_para_init(gpio_init_type *gpio_init_struct);
- 功能描述 初始化 GPIO 默认参数
- 输入参数 gpio_init_struct:指向结构体 gpio_init_type 的指针
- 输出参数 无
- 返回值 无
其中gpio_init_type 在 at32f435_437_gpio.h 中定义
typedef struct
{
uint32_t gpio_pins;
gpio_output_type gpio_out_type;
gpio_pull_type gpio_pull;
gpio_mode_type gpio_mode;
gpio_drive_type gpio_drive_strength;
} gpio_init_type;
- 5.14.2 函数 gpio_init
- 函数原型 void gpio_init(gpio_type *gpio_x, gpio_init_type *gpio_init_struct);
- 功能描述 初始化 GPIO 外设
- 输入参数 1 gpio_x:所选择的 GPIO 外设,该参数可以选取自其中之一:
GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG, GPIOH - 输入参数 2 gpio_init_struct:指向结构体 gpio_init_type 的指针
- 输出参数 无
- 返回值 无
- at32_led_toggle、at32_led_off、at32_led_on等控制函数
- 其是通过直接控制GPIO的“GPIO输出数据寄存器(GPIOx_ODT)”、“GPIO设置/清除寄存器(GPIOx_SCR)”、“GPIO位清除寄存器(GPIOx_CLR)”来实现的。
(2) 各库函数对应的寄存器
库函数最终是通过调用寄存器来实现目标功能的。按理说一般嵌入式开发中使用库函数也就可以了。但实际情况显然比这要复杂,寄存器的学习仍然少不了。一方面,寄存器是真正涉及芯片物理层面的东西,虚拟化的东西伴随着不确定性,现实的却是清清楚楚。换句话说,寄存器是比库函数更底层的,而底层往往拥有俯瞰顶层的优势。尤其是在接触不同类型单片机时,知其所以然很重要。另一方面,大型的工程为了保证兼容性、可移植性,往往都少不了寄存器的参与。
参考“6、AT32官方资料\(1)教程与文档\(2)AT32F435 参考手册(寄存器).pdf”(以下简称参考手册),以下给出各库函数对应寄存器与其在文档中的位置。
-
- 函数 crm_periph_clock_enable
- 4.3.10 AHB外设时钟使能寄存器1(CRM_AHBEN1)~ 4.3.14 APB2外设时钟使能寄存器(CRM_APB2EN)
这4个寄存器用于使能与失能不同总线上的外设,每个位都代表一种外设,置1开启,置0关闭。
AT32各外设的寄存器地址与其所挂载总线(即AHB、APB1、APB2等)的位置,可以在“2.4 外设地址映射”中进行查看。其中 时钟和复位管理(CRM)外设挂载在AHB1总线下,在手册中“AHB 总线矩阵”既有AHB1的部分也有AHB2的部分。AHB1地址为0x4000 2000 ~ 0x4FFF FFFF, AHB2地址为:0x5000 0000 ~ 0xC000 0000
-
- 函数 gpio_init
这里使用到了GPIO外设(包括GPIOA、GPIOB……),其都挂载于AHB1总线下。
-
6.3.1 GPIO配置寄存器(GPIOx_CFGR)(x=A…H)
每两个位为一组,用于控制GPIO的模式,一个寄存器(如GPIOA_CFGR)32位,则可控制16个IO
GPIO的模式有:
00:输入(复位后的模式)
01:通用输出
10:复用功能
11:模拟
其中复用模式,是复用为SPI、USART等功能,模拟模式是用于ADC、DAC功能(?) -
6.3.2 GPIO输出模式寄存器(GPIOx_OMODE)(x=A…H)
CFGR寄存器配置为输出模式,OMODE才有作用。
这个寄存器只用到了低16位,第个位控制一个IO
0:推挽(复位状态)
1:开漏 -
6.3.3 GPIO电流推动/吸入能力切换控制寄存器(GPIOx_ODRVR)
(x=A…H)
每两个位为一组,用于配置相应的 I/O 端口承受电流能力:
x0:适中电流推动/吸入能力
01:较大电流推动/吸入能力
11:适中电流推动/吸入能力 -
6.3.4 GPIO上/下拉寄存器(GPIOx_PULL)(x=A…H)
每两个位为一组,用于配置相应的 I/O 端口上拉、下拉、浮空等。
00:无作用
01:上拉
10:下拉
-
- at32_led_toggle、at32_led_off、at32_led_on等控制函数
-
6.3.6 GPIO输出数据寄存器(GPIOx_ODT)(x=A…H)
每一位对应 GPIOx 的一个 IO,置1输出高电平,置0转出低电平。 -
6.3.7 GPIO设置/清除寄存器(GPIOx_SCR)(x=A…H)
包含两类设置位:- IOCB(位 31:16)
写’1’的位其对应 ODT 寄存器位会清除,写‘0’的位其对应
ODT 寄存器位维持不变。 - IOSB(位 15:0)
写’1’的位其对应 ODT 寄存器位会置起,写‘0’的位其对应
ODT 寄存器位维持不变。如果 IOCB 和 IOSB 同一个位都写’1’,那么IOSB 会生效。
- IOCB(位 31:16)
- at32_led_toggle、at32_led_off、at32_led_on等控制函数
3.1.2 硬件设计
图3.1.2.1
图3.1.2.2
如图可知,LED1连接到了PD14上,LED2连接到了PD15上。且IO口输出高电平LED点亮,输出低电平LED熄灭。
3.1.3 软件设计
(1)led的初始化与控制
“qy_drivers/led.c”是led的驱动文件,led初始化函数如下:
/**
* @brief configure led gpio
* @param led: specifies the led to be configured.
* @retval none
*/
void at32_led_init(led_type led)
{
gpio_init_type gpio_init_struct;
/* enable the led clock */
crm_periph_clock_enable(led_gpio_crm_clk[led], TRUE);
/* set default parameter */
gpio_default_para_init(&gpio_init_struct);
/* configure the led gpio */
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_OUTPUT;
gpio_init_struct.gpio_pins = led_gpio_pin[led];
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(led_gpio_port[led], &gpio_init_struct);
}
可以看到使用了之前介绍的crm_periph_clock_enable(11行)与gpio_init(22行)函数。而gpio_init_type类型变量的子项(17~21行)与GPIO的寄存器是一一对应的。
led电平翻转函数如下:
/**
* @brief turns selected led toggle.
* @param led: specifies the led to be set off.
* this parameter can be one of following parameters:
* @arg LED2
* @arg LED3
* @arg LED4
* @retval none
*/
void at32_led_toggle(led_type led)
{
if(led > (LED_NUM - 1))
return;
if(led_gpio_pin[led])
led_gpio_port[led]->odt ^= led_gpio_pin[led];
}
使用到了GPIO输出数据寄存器(GPIOx_ODT)。
与之类似的,at32_led_off、at32_led_on函数分别使用CLR、SCR寄存器来控制IO口。
/**
* @brief turns selected led on.
* @param led: specifies the led to be set on.
* this parameter can be one of following parameters:
* @arg LED2
* @arg LED3
* @arg LED4
* @retval none
*/
void at32_led_on(led_type led)
{
#ifdef QY_BY_CORE_BOARD //AT32核心板
if(led > (LED_NUM - 1))
return;
if(led_gpio_pin[led])
led_gpio_port[led]->clr = led_gpio_pin[led];
#else //AT32飞控
if(led > (LED_NUM - 1))
return;
if(led_gpio_pin[led])
led_gpio_port[led]->scr = led_gpio_pin[led];
#endif
}
第12行为一个预编译指令,判断是否定义了QY_BY_CORE_BOARD宏。QY_BY_CORE_BOARD宏定义在“qy_tasks -》 qy_common_config.h”中,如果定义了就表示使用AT32核心板,未定义表示使用AT32飞控。由于两个板子的LED电路并不相同,故加入这个判断。
(2)main.c文件
回到main.c文件,编写如下代码:
#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();
#ifdef QY_BY_CORE_BOARD //AT32核心板
/****************************************** LED初始化 ************************************************/
My_LED_init();
#else //AT32飞控
/****************************************** LED初始化 ************************************************/
My_LED_init();
#endif
/* 创建其他任务 */
qy_tasks_creat();
vTaskDelete(START_handler); //删除开始任务
/* 退出临界区 */
taskEXIT_CRITICAL();
}
-
行1:
这个头文件包含了可能用到的包括AT32库、FreeRTOS、Qyflight在内的所有头文件。 -
行14~24:
main函数中,初始化系统时钟,调用了My_LED_init()函数初始化LED,其后中创建开始任务,并最后进行任务调度。其中My_LED_init()函数最终调用了at32_led_init()函数初始化LED。 -
行27~55:
为开始任务,主要是初始化LED与创建其他任务。
这里将外设的初始化都放在一个任务中,原因是只有在任务函数中(可能还有中断中)才能调用FreeRTOS的vTaskDelay()延时函数。当然,通过一些技巧,也可以在保证FreeRTOS正常运行的情况下,使用Systick编写自定义的延时函数。-
行43:
调用了My_LED_init()函数初始化LED,而My_LED_init()函数最终调用了at32_led_init()函数初始化LED。 -
行52:
定义在qy_tasks.c中
-
(3)任务调用
/**
**************************************************************************
* qy_tasks
* @brief 任务管理文件
* @writer 清月默默
* @time 2023.12.6
**************************************************************************
*/
#include "qy_common_header.h"
//测试任务设置
TaskHandle_t Test_handler; //任务句柄
void Test_task_function(void *pvParameters); //任务函数
void qy_tasks_creat(void)
{
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*/
}
/* 测试任务函数 */
void Test_task_function(void *pvParameters)
{
while(1)
{
#ifdef QY_BY_CORE_BOARD //AT32核心板
vTaskDelay(100);
#else //AT32飞控
vTaskDelay(100);
#endif
}
}
行16~21:
创建了两个任务,其中“Test_task”任务用于程序调试,这里未用到。"State_show_task"状态显示任务的任务函数State_show_task_function定义在“qy_tasks/qy_State_show_tasks.c”中。
/**
**************************************************************************
* qy_State_show_tasks
* @brief 任务管理文件
* @writer 清月默默
* @time 2023.12.6
**************************************************************************
*/
#include "qy_common_header.h"
TaskHandle_t State_show_handler; //任务句柄
void State_show_task_function(void *pvParameters); //任务函数
/* LED任务函数 */
void State_show_task_function(void *pvParameters)
{
while(1)
{
#ifdef QY_BY_CORE_BOARD //AT32核心板
at32_led_toggle(LEDG);
vTaskDelay(100);
at32_led_toggle(LEDB);
vTaskDelay(100);
at32_led_toggle(LEDY);
vTaskDelay(100);
#else //AT32飞控
at32_led_toggle(LED1);
vTaskDelay(100);
at32_led_toggle(LED2);
vTaskDelay(100);
vTaskDelay(100);
#endif
}
}
状态显示任务主要功能是显示系统运行状态,用于提示。之后可能加入蜂鸣器相关程序。这里暂时只实现了LED流水灯式亮灭。
3.1.4 下载验证
按住BOOT按键,给飞控上电,进入Bootloader(同时进入USB的DFU模式)。编译例程“1_LED”,双击“Download_tool_ISP”文件夹下的“1_Download_by_usb.bat”,开始下载程序。下载完成后AT32飞控的两个LED会呈现流水灯闪烁的效果。
开发思考
两个LED,左侧的一直闪烁,用于表示系统正常运行。右则的用于示警等。
使用PWM控制LED实现渐变效果。
;
vTaskDelay(100);
at32_led_toggle(LED2);
vTaskDelay(100);
vTaskDelay(100);
#endif
}
}
状态显示任务主要功能是显示系统运行状态,用于提示。之后可能加入蜂鸣器相关程序。这里暂时只实现了LED流水灯式亮灭。
### 3.1.4 下载验证
按住BOOT按键,给飞控上电,进入Bootloader(同时进入USB的DFU模式)。编译例程“1_LED”,双击“Download_tool_ISP”文件夹下的“1_Download_by_usb.bat”,开始下载程序。下载完成后AT32飞控的两个LED会呈现流水灯闪烁的效果。
### 开发思考
两个LED,左侧的一直闪烁,用于表示系统正常运行。右则的用于示警等。
使用PWM控制LED实现渐变效果。