简介:STM32F103VET6是一款基于ARM Cortex-M3内核的微控制器,适用于嵌入式系统设计。本实验例程将指导开发者在STM32F103VET6上运行实时操作系统UCOS,实现CAN通信和图形用户界面ucGUI。学习内容包括微控制器初始化、任务调度、信号量、互斥锁、邮箱、消息队列等实时操作系统功能,以及如何配置CAN通信模块和设计交互式的图形用户界面。此例程包含完整的源代码文件,如main.c、can.c/h、ucos_config.h等,帮助学习者深入理解STM32F103VET6的操作、UCOS的原理以及CAN通信和图形界面的设计与实现。
1. STM32F103VET6微控制器特性
STM32F103VET6是ST公司生产的一款中等性能的ARM Cortex-M3内核微控制器,广泛应用于工业控制、医疗设备、通信设备等领域。这款微控制器的主要特点包括丰富的外设接口、强大的处理能力以及灵活的电源管理,使其在多种应用场景中展现出极高的性能和可靠性。
1.1 核心特性
STM32F103VET6的核心特性包括: - ARM Cortex-M3 32位RISC处理器 - 最高达72MHz的运行频率 - 512KB的闪存,64KB的RAM存储空间 - 多达112个I/O端口,丰富的通信接口
1.2 硬件资源
在硬件资源配置方面,STM32F103VET6提供了: - 11个定时器,包括3个高级控制定时器 - 2个I2C总线、3个USART、2个SPI和一个CAN总线接口 - 2个ADC,具有12位分辨率,最多16个通道
1.3 开发环境
对于开发者而言,STM32F103VET6拥有强大的开发支持,包括: - ST提供的官方集成开发环境(IDE):Keil MDK-ARM、IAR EWARM - 开源社区支持的工具链:GCC-based ARM Embedded Toolchain
综上所述,STM32F103VET6微控制器凭借其强大的性能、丰富的外设资源以及完善的支持工具,成为嵌入式系统设计的理想选择。在后续章节中,我们将探讨如何利用这些特性,结合UCOS实时操作系统、CAN通信协议和ucGUI图形用户界面库,进行系统级的设计与实现。
2. UCOS实时操作系统基本概念及实现
2.1 UCOS实时操作系统简介
2.1.1 UCOS的发展历史和特性
UCOS(通常指的是uC/OS),也称为微控制器操作系统,是由Jean J. Labrosse开发的。它是在1992年首次发布的实时内核,最初设计用于8051微控制器。由于其源代码完全公开且注释详尽,被广泛应用于学术研究与工业控制。uC/OS-II和uC/OS-III是其两个主要版本,它们提供了多种实时操作系统服务,例如任务调度、同步和通信等。uC/OS-II是一种抢占式实时内核,而uC/OS-III则扩展了uC/OS-II,提供了更多的特性,比如更高级的资源管理功能。
uC/OS具有以下特性:
- 任务调度:支持多任务,可抢占式和时间片轮转调度。
- 同步和通信机制:信号量、互斥量、消息队列等。
- 内存管理:提供静态和动态内存分配策略。
- 内核可裁剪:能够根据需要裁剪功能,以减少内核大小。
- 可配置性:大部分特性都可以根据项目需求进行配置。
2.1.2 UCOS在STM32F103VET6上的移植与配置
要在STM32F103VET6微控制器上移植和配置uC/OS,首先需要下载uC/OS源代码,并将其集成到STM32的开发环境中。STM32F103VET6通常使用Keil MDK、IAR、GCC等IDE进行开发,因此需要确保uC/OS源代码兼容所选IDE。
移植过程大致如下:
- 配置STM32的硬件时钟系统和中断管理器,确保时钟源和中断向量表符合uC/OS的要求。
- 根据需要调整uC/OS的配置文件,例如设置任务的数量、堆栈大小等。
- 将uC/OS的源代码文件添加到项目中,并确保所有必要的文件都已正确引用。
- 编写启动代码,包括uC/OS的启动函数,确保在main()函数之前调用。
- 编写系统初始化代码,包括设置系统时钟和外设。
- 实现任务创建和调度,并在应用程序中调用uC/OS的相关API。
#include "os.h"
#include "stm32f10x.h"
// 初始化系统时钟
void SysClock_Config(void) {
// 省略具体实现代码
}
int main(void) {
osKernelInit(); // 初始化uC/OS内核
SysClock_Config(); // 配置系统时钟
// 创建任务、信号量、消息队列等
osKernelStart(); // 启动uC/OS内核
while(1) {
}
}
代码中 osKernelInit()
、 osKernelStart()
、 SysClock_Config()
等函数调用是移植uC/OS必须执行的步骤,以确保内核可以正常启动和运行。
2.2 UCOS操作系统的核心机制
2.2.1 任务调度与管理
任务调度是实时操作系统最核心的功能之一。uC/OS通过任务控制块(TCB)来管理任务,每个任务都有一个唯一的TCB,其中包含了任务的状态、优先级、堆栈指针、任务函数指针等信息。
任务调度的步骤主要包括:
- 初始化任务:为每个任务分配TCB和堆栈空间,设置任务的优先级。
- 创建任务:通过调用
OSTaskCreate()
函数创建任务。 - 调度任务:uC/OS的调度器在任务切换时,会根据任务的优先级来决定哪个任务获得CPU执行时间。
- 删除任务:当任务完成或不再需要时,通过
OSTaskDel()
函数删除任务。
void Task1(void *p_arg) {
// 省略具体任务代码
}
int main(void) {
OSInit(); // 初始化uC/OS内核
OSTaskCreate(Task1, NULL, &Task1Stk[STACK_SIZE - 1], TASK1_PRIORITY);
OSStart(); // 启动任务调度
}
在上述代码中, OSInit()
初始化内核, OSTaskCreate()
创建一个任务, OSStart()
启动内核中的调度器,从而使得任务得以执行。
2.2.2 中断管理和服务
中断管理是实时操作系统响应外部事件的一种方式。uC/OS通过提供API来管理中断,其中包括中断禁用( OSIntEnter()
)和中断启用( OSIntExit()
)函数。在中断服务程序(ISR)中,开发者可以调用这些函数来处理中断,同时通知uC/OS中断服务已经完成。
任务可以通过信号量和消息队列等机制来等待中断的发生,从而实现任务与中断的同步。
2.2.3 内存管理策略
uC/OS提供了静态和动态内存分配的策略,使得开发者可以根据具体的应用场景选择合适的内存管理方式。
静态内存分配通常在系统初始化时完成,具有确定性,但是不够灵活。动态内存分配则允许在运行时分配和释放内存块,提高了内存使用的灵活性,但是可能会引起内存碎片问题。
void *ptr;
ptr = OSMalloc(100); // 动态分配100字节内存
OSFree(ptr); // 释放内存
在上述代码中, OSMalloc()
函数用于动态分配内存,而 OSFree()
函数用于释放内存。这些函数的实现确保了内存的正确分配和管理,从而避免了内存泄露等问题。
2.3 UCOS的实践应用
2.3.1 基本任务的创建和执行
创建一个任务意味着分配必要的资源,包括内存和CPU时间。在uC/OS中创建任务通常需要指定任务函数、堆栈大小、优先级等参数。任务函数是一个无限循环,它通常包含多个执行路径,这些路径可以通过条件语句或状态机来实现。
任务创建的代码示例如下:
void Task1(void *p_arg) {
for (;;) {
// 执行任务代码
OSTimeDlyHMSM(0, 0, 1, 0); // 延时1秒
}
}
int main(void) {
OSInit(); // 初始化uC/OS内核
OSTaskCreate(Task1, NULL, &Task1Stk[STACK_SIZE - 1], TASK1_PRIORITY);
OSStart(); // 启动任务调度
}
在这个任务函数中,我们使用了 OSTimeDlyHMSM()
函数来实现任务的延时功能,这是uC/OS中实现任务时间管理的常用方法。
2.3.2 信号量、消息队列的使用
信号量和消息队列是uC/OS提供的两种基本同步机制。信号量通常用于实现任务间的互斥和同步,而消息队列则用于实现任务间的数据交换和通信。
OS_EVENT *sem;
OS_EVENT *mq;
sem = OSSemCreate(1); // 创建一个初始计数为1的信号量
mq = OSMboxCreate((void *)0); // 创建一个空的消息队列
void Task2(void *p_arg) {
// 等待信号量
OSSemPend(sem, 0, &err);
// 发送消息到消息队列
OSMboxPost(mq, (void *)msg);
}
void Task3(void *p_arg) {
// 发送信号量
OSSemPost(sem);
// 接收消息队列中的消息
msg = (int)OSMboxPend(mq, 0, &err);
}
在上述代码中, OSSemCreate()
创建信号量, OSSemPend()
和 OSSemPost()
分别用于等待和发送信号量。 OSMboxCreate()
创建消息队列, OSMboxPost()
和 OSMboxPend()
分别用于发送和接收消息。
2.3.3 定时器与时间管理
uC/OS中的定时器是实现任务定时或周期性执行的重要机制。uC/OS提供了多种定时器功能,包括一次性定时器、周期性定时器等。
OS_TMR tmr;
void TimerTask(void *p_arg) {
(void)p_arg;
for (;;) {
OSTmrTimeLeft(&tmr); // 获取定时器剩余时间
OSTmrStart(&tmr, 1000); // 重新启动定时器,定时1000个时钟节拍
}
}
void main(void) {
OSInit();
tmr.TmrNext = OS_TmrTick + 1000; // 初始化定时器
OSTmrCreate(&tmr, "Timer Task", 1000, 0, OS_TIMERTASK_STK_SIZE,
(void *)TimerTask, (void *)0, &err);
OSStart();
}
在上述代码中, OSTmrCreate()
创建了一个定时器, OSTmrStart()
和 OSTmrStop()
分别用于启动和停止定时器, OSTmrTimeLeft()
用于获取定时器剩余时间。这些函数使得开发者可以精确地控制定时器的行为。
通过本章节的介绍,我们深入了解了uC/OS实时操作系统的架构和基本概念,以及如何在STM32F103VET6微控制器上进行移植和配置。接下来,我们将继续探索uC/OS的核心机制,包括任务调度、中断管理、内存管理等,以及如何在实际应用中创建和管理任务,使用信号量、消息队列和定时器,为构建可靠的实时系统打下坚实基础。
3. CAN通信协议在STM32F103VET6上的配置与应用
在当今的嵌入式系统中,数据的可靠传输和设备间的通信是至关重要的。在工业自动化、汽车电子以及其他许多领域,CAN通信协议因其高效、可靠和具有错误检测能力的特性而被广泛采用。STM32F103VET6微控制器由于其强大的性能和丰富的功能,成为实现CAN通信的理想平台。
3.1 CAN通信协议概述
3.1.1 CAN协议的特点和应用场景
控制器局域网络(CAN)是一种被广泛使用的、允许微控制器和设备之间进行通信的网络协议。CAN协议最显著的特点包括:
- 高效率:CAN协议使用非破坏性的仲裁方法,可以同时处理多个消息而无需等待确认,极大提高了数据吞吐量。
- 高可靠性:具有错误检测和处理机制,确保数据传输的准确性和完整性。
- 实时性:CAN协议特别适用于要求高实时性的场合,如汽车制动系统。
- 抗干扰能力强:采用差分信号传输,对电磁干扰有很好的抵抗能力。
CAN协议在汽车电子(发动机控制、ABS系统等)、工业自动化(传感器网络、远程I/O等)和医疗器械(患者监护系统)等多种场景中得到应用。
3.1.2 STM32F103VET6的CAN模块架构
STM32F103VET6微控制器内置了CAN模块,支持CAN2.0A和CAN2.0B协议,提供了灵活的过滤器和接收FIFO功能,能够方便地实现复杂的通信需求。该模块拥有两个独立的CAN核心,可以配置为灵活数据长度或标准数据长度,以及具有高精度的时钟同步功能。
接下来我们将探讨如何在STM32F103VET6上进行CAN通信的配置与初始化。
3.2 CAN通信的配置和初始化
3.2.1 CAN引脚配置与初始化代码解析
CAN模块的配置开始于引脚的配置。在STM32F103VET6上,CAN模块的发送和接收引脚需要正确配置。代码块1展示了基本的引脚配置过程:
// 代码块1:CAN引脚配置
void CAN_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_TimeInitTypeDef CAN_TimeInitStructure;
// 1. 使能GPIOB和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 2. 配置PB8为复用推挽输出(主功能为CAN1_RX),PB9为复用推挽输出(主功能为CAN1_TX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 3. 使能CAN1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// 4. 初始化CAN
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_4tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 4; // 预分频值,此处为4
CAN_Init(CAN1, &CAN_InitStructure);
// 5. 初始化CAN时间触发模式(可选)
CAN_TimeInitStructure.CAN_TPrescaler = 0x10;
CAN_TimeInit(CAN1, &CAN_TimeInitStructure);
// 6. 激活CAN进入初始化模式
CANCmd(CAN1, ENABLE);
}
在上面的代码块中,我们首先初始化了GPIOB的相关引脚(PB8和PB9),使其成为复用推挽输出模式。接下来,我们启动了CAN1的时钟,并对CAN模块进行了基础的配置。配置包括CAN的总线定时器设置,其中CAN_BS1、CAN_BS2和CAN_SJW设置了不同的时间段。最后,我们启动了CAN模块。
3.2.2 CAN过滤器设置及报文发送接收
在CAN总线系统中,过滤器可以确保消息只被具有相应标识符的接收器接收。以下示例展示了如何配置CAN过滤器,并进行基本的报文发送和接收操作。
// 代码块2:CAN过滤器配置及发送接收示例
void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
// 1. 配置CAN过滤器
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
// 2. 发送CAN消息
CAN_TxMsg TxMessage;
TxMessage.StdId = 0x321; // 标准ID
TxMessage.IDE = CAN_ID_STD;
TxMessage.RTR = CAN_RTR_DATA;
TxMessage.DLC = 8; // 数据长度为8字节
uint8_t data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
for (uint8_t i = 0; i < TxMessage.DLC; i++)
{
TxMessage.Data[i] = data[i];
}
CAN_Transmit(CAN1, &TxMessage);
// 3. 接收CAN消息
CAN_RxMsg RxMessage;
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 处理接收到的数据
}
}
在代码块2中,我们首先配置了一个基本的CAN过滤器,该过滤器会接收所有ID的消息。然后,我们创建并发送了一个标准的CAN消息。最后,我们在CAN FIFO0中等待接收消息。一旦有消息到达,我们读取该消息并可以对它进行处理。
3.3 CAN通信的高级应用
3.3.1 CAN网络的故障诊断与处理
CAN通信协议提供了强大的错误检测功能,包括循环冗余检查(CRC)、帧检查、ACK检查等。STM32F103VET6的CAN模块能够检测以下类型的错误:
- 位错误:当一个发送器在发送一个显性位(逻辑1)时,检测到一个隐性位(逻辑0)。
- 填充错误:在任何固定的填充位中检测到一个与预定填充模式不同的位。
- 格式错误:帧格式不正确。
- 应答错误:发送器在发送请求帧或远程帧后未接收到应答位。
接下来,我们将看到如何在代码中处理这些错误,以及如何对CAN总线进行故障诊断。
3.3.2 CAN总线的扩展与安全性设计
CAN总线的扩展可能包括增加网络上的节点数量、延伸传输距离或增加网络的可靠性。在STM32F103VET6上,可以使用CAN网络的扩展功能来实现这些目标。安全性设计则需要考虑防止数据被篡改、确保数据传输的完整性等方面。STM32F103VET6提供了多种硬件和软件机制来保护CAN通信的安全性,比如使用硬件流控制和加入加密机制。
以上就是本章对CAN通信协议在STM32F103VET6上的配置与应用的介绍。下一章将介绍如何在STM32F103VET6上应用ucGUI图形用户界面库,以及如何将ucGUI集成到我们的项目中。
4. ucGUI图形用户界面库的应用与自定义
4.1 ucGUI图形库简介
4.1.1 ucGUI的特点与架构
ucGUI (Microcontroller Graphics Library) 是一款专为嵌入式系统设计的图形用户界面库,它为开发者提供了一系列工具和函数,用于创建图形化用户界面。它支持多语言显示、窗口管理、控件支持、图形绘制、触摸屏支持等丰富功能。其特点在于占用资源小、移植性强,能够支持从8位到32位的多种微控制器,是嵌入式系统开发中常用的图形库之一。
ucGUI的架构设计是模块化的,主要分为以下几层:
- 核心层:提供了ucGUI的基础功能,如内存管理、配置管理等。
- 控件层:提供了一系列标准控件,如按钮、滑动条、编辑框等。
- 图形窗口层:包含了窗口管理、绘图函数等,是实现图形界面的核心。
- 扩展层:包含字体管理、字体编码转换等高级功能。
4.1.2 ucGUI在STM32上的集成与配置
在STM32平台上集成ucGUI图形库,首先需要下载ucGUI库的源代码,然后将其添加到项目中。接着,需要进行库的配置,包括内存配置、显示驱动的配置以及输入设备(如按键或触摸屏)的配置。配置完成后,开发者可以通过调用ucGUI提供的API函数来设计和实现图形用户界面。
#include "GUI.h"
void GUI_Init(void) {
// 初始化图形库环境
GUI_Init();
// 设置显示驱动
GUI_SetFunc_Drv(GUIlesenDrawDriver, GUIlesenMouseDriver);
// 加载字体
GUI_SetFont(GUIlesenFont14);
// 清屏
GUI_Clear();
}
int main(void) {
// 硬件初始化
SystemInit();
// 初始化ucGUI
GUI_Init();
// 创建一个窗口
GUI_CreateWindow(0, 0, 240, 320);
// 进入ucGUI的主消息循环
while(1) {
GUI.DataBind();
}
}
4.2 ucGUI界面设计基础
4.2.1 基础控件的使用与设计
在设计界面时,开发者可以使用ucGUI提供的基础控件。基础控件包括静态文本、按钮、列表框、进度条等。例如,创建一个按钮控件的函数如下:
void CreateButton(GUI_PIDirtyRect *pRect) {
GUI creadoButton;
GUI_CreateButton(pRect->x0, pRect->y0, pRect->x1, pRect->y1, "Click Me", 0, GUI_ANY);
}
其中, GUI_CreateButton
函数定义了按钮的边界和文本,而 GUI_PIDirtyRect
是一个结构体,用来存储控件的位置和大小。在实际使用中,开发者需要根据具体界面布局定义控件的位置和尺寸。
4.2.2 界面的布局与美化技巧
布局方面,可以使用ucGUI的容器控件,如窗口(Window)和面板(Panel),这些容器可以将控件分组,使得布局更加整洁和有序。此外,ucGUI提供了灵活的布局管理功能,如自动布局(Auto Layout)、比例布局等,可以根据显示设备的不同自动调整控件的位置和大小。
美化方面,开发者可以使用不同颜色、字体和图片。ucGUI支持TrueType字体和位图字体,可以通过 GUI_SetFont
函数来设置。同时,可以在创建控件时指定颜色属性,如背景色和前景色。
4.3 ucGUI高级功能开发
4.3.1 动态资源加载与管理
在设计复杂的用户界面时,资源的动态加载变得至关重要。ucGUI提供了动态资源管理的接口,允许开发者在运行时加载和卸载图像、字体、字符串表等资源。以下是一个动态加载图像资源的示例:
void LoadImage(GUI_PIDirtyRect *pRect, const char* imageFileName) {
GUI_CreatePic(pRect->x0, pRect->y0, pRect->x1, pRect->y1, imageFileName);
}
这里 GUI_CreatePic
函数根据传入的文件名动态加载图像到指定位置。在动态资源管理中,应该注意及时卸载不再使用的资源,以减少内存占用。
4.3.2 自定义控件与扩展库开发
当标准控件不能满足特定需求时,开发者可以通过继承和扩展ucGUI的基础控件来创建自定义控件。自定义控件可以拥有独特的外观和行为,以适应特定的用户界面需求。以下是自定义控件的一个简单示例:
typedef struct {
GUI_Object _Super;
int myValue;
} MyCustomControl;
void MyCustomControlDraw(MyCustomControl *pThis) {
// 绘制控件的自定义外观
GUI_ClearRect(pThis->x0, pThis->y0, pThis->x1, pThis->y1);
GUI_SetColor(GUI_BLACK);
GUI_SetFont(GUIlesenFont14);
GUI_DispStringAt("Custom Control", pThis->x0 + 5, pThis->y0 + 5);
}
int MyCustomControlHandleMessage(GUI_Object *pObj, GUI_MESSAGE*pMsg) {
MyCustomControl *pThis = (MyCustomControl *)pObj;
switch(pMsg->MsgId) {
case GUI_MSG_ID_PAINT:
MyCustomControlDraw(pThis);
return 1;
default:
return 0;
}
}
MyCustomControl *MyCustomControlCreate(int x0, int y0, int x1, int y1) {
MyCustomControl *pThis = GUI_malloc(sizeof(MyCustomControl));
pThis->myValue = 0;
GUI_ObjectConstruct(&pThis->_Super, MyCustomControlHandleMessage, x0, y0, x1, y1);
return pThis;
}
在自定义控件中, GUI_ObjectConstruct
用于构造对象, MyCustomControlHandleMessage
函数处理控件的消息, MyCustomControlDraw
函数定义了控件的绘制方法。开发者可以根据这个框架继续添加新的属性和方法,以创建更复杂的行为逻辑。
5. 主要源代码文件介绍
5.1 代码文件结构解析
5.1.1 项目文件目录概览
在开发STM32F103VET6基于UCOS、CAN通信协议和ucGUI的应用时,代码的组织结构是关键。良好的文件组织可以帮助开发者快速定位和管理项目中的各个部分。项目文件通常包含以下几个主要部分:
- Source Code :存放所有的源代码文件,包括实现UCOS核心机制的
.c
文件、CAN通信处理的.c
文件、以及ucGUI界面实现的.c
文件等。 - Header Files :存放所有的头文件
.h
,这些头文件中定义了各种功能模块的接口和数据结构。 - Drivers :存放与硬件平台相关的驱动程序,例如,与STM32F103VET6的GPIO、USART、ADC等硬件模块相关的驱动代码。
- Middlewares :中间件目录,存放第三方库文件,如UCOS源码、ucGUI图形库文件等。
- Inc :存放通用的头文件,比如系统配置、常量定义、宏定义等。
- Lib :存放编译后生成的库文件。
- Application :应用程序目录,存放实现特定功能的主程序代码和配置文件。
- Doc :存放项目文档,包括设计说明、用户手册、开发日志等。
在Source Code和Drivers目录下,代码通常会按照功能模块进一步组织成子目录。例如,UCOS任务管理模块的代码可能会放在一个名为 os_core
的子目录下,而CAN通信模块的代码则可能放在一个名为 can_driver
的子目录下。
5.1.2 主要模块代码文件解析
对于主要模块的代码文件,我们可以按照如下方式组织和解析:
- UCOS任务管理模块 :
-
os_core.c
:实现任务调度、任务创建、任务管理等核心功能。 -
os_core.h
:定义任务控制块(TCB)、事件控制块(ECB)等结构体,以及任务操作的API。 - CAN通信模块 :
-
can_driver.c
:实现CAN总线的初始化、报文的发送和接收等。 -
can_driver.h
:定义CAN相关的数据结构和API。 -
ucGUI界面实现模块 :
-
gui_driver.c
:包含ucGUI的初始化、界面绘制和事件处理等核心功能。 -
gui_driver.h
:包含与ucGUI图形界面相关的数据结构和API。
每个模块的实现代码文件( .c
文件)应该具有清晰的结构,遵循函数注释规范,以便于其他开发者理解代码的意图和功能。同时,通过合理的函数划分和模块化设计,可以提高代码的复用性和可维护性。
5.2 核心模块的实现代码分析
5.2.1 UCOS任务管理模块代码详解
以UCOS任务管理模块中的任务创建函数为例,我们可以分析其代码实现:
#include "os_core.h"
void OSTaskCreate(OSTCB *ptcb, void (*pfunc)(void *), void *parg, INT16U prio) {
// 分配任务控制块TCB
if(ptcb == NULL) {
return;
}
// 初始化任务控制块
ptcb->prio = prio;
ptcb->pfunc = pfunc;
ptcb->parg = parg;
ptcb->sp = 0; // 任务栈指针初始化
ptcb->state = OS_TASK_STOPPED; // 任务状态设置为停止
// 根据任务优先级插入就绪列表
// ...
}
此函数为创建一个新任务,首先检查任务控制块指针是否有效。接着,初始化任务控制块,包括任务优先级、任务函数指针、传递给任务的参数、任务栈指针和任务状态。最后,需要根据任务的优先级将其插入到就绪列表中,使任务能够在调度器中被选择执行。
5.2.2 CAN通信模块代码详解
CAN通信模块的代码实现需要考虑到STM32F103VET6的硬件特性。在初始化过程中,我们需要配置CAN控制器的相关参数。
#include "can_driver.h"
void CAN_Config(void) {
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
// CAN初始化结构配置
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_4tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 4; // 预分频器值
CAN_Init(CAN1, &CAN_InitStructure);
// CAN过滤器配置
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
上述代码段展示了如何配置STM32的CAN控制器,包括时间触发通信模式、自动离线管理、自动唤醒、非接收列表模式、时间滤波器、发送优先级等。同时,设置了CAN总线的同步跳宽、时间段1、时间段2和预分频器值。最后,对CAN过滤器进行初始化,设置过滤器模式、比例、ID以及过滤器激活状态。
5.2.3 ucGUI界面实现模块代码详解
ucGUI界面实现模块涉及到图形界面的构建,其中包括窗口、控件的创建和消息的处理。
#include "gui_driver.h"
void GUI_Init(void) {
GUI_Init(); // 初始化ucGUI
GUI_SetBkColor(GUI_BLACK); // 设置背景颜色为黑色
GUI_Clear(); // 清除显示内容
GUI_SetColor(GUI_WHITE); // 设置绘图颜色为白色
GUI_SetFont(&GUI_Font24_ASCII); // 设置字体为24像素ASCII字体
// 创建窗口
GUI_CreateWindow(0, 0, 240, 320, "My Window", 0);
// 绘制字符串
GUI_DispStringAt("Hello, ucGUI!", 10, 10);
}
void MyEvent(void) {
// 事件处理逻辑
// ...
}
在这段代码中,首先调用 GUI_Init
函数初始化ucGUI系统。然后设置背景和绘图颜色,选择字体。创建一个名为"My Window"的窗口,并在窗口中显示字符串"Hello, ucGUI!"。实际应用中,还需要编写相应的事件处理函数,如 MyEvent
,来响应用户输入或系统消息。
5.3 代码优化与调试技巧
5.3.1 代码优化策略与方法
代码优化是提高程序性能、减少资源消耗和提升用户体验的重要环节。在STM32F103VET6上使用UCOS、CAN通信协议和ucGUI时,可以从以下几个方面进行代码优化:
- 静态内存分配 :避免使用动态内存分配,减少内存碎片和分配失败的风险。
- 循环展开 :减少循环开销,尤其是在循环体执行频繁或循环次数较少时。
- 宏定义优化 :使用宏定义减少重复代码,提高编译器优化效率。
- 函数内联 :对于小函数,内联可以减少函数调用的开销。
- 中断优先级和任务优先级配置 :合理安排中断服务程序和任务的优先级,减少任务切换的开销。
在实际开发中,应结合代码分析工具(如gprof、Valgrind等)和性能测试结果,有针对性地进行优化。
5.3.2 调试技巧与常见错误分析
调试是软件开发中不可或缺的一部分。在STM32F103VET6的开发中,可以通过以下调试技巧来发现和解决问题:
- 串口打印 :利用串口输出关键变量的值,观察程序执行流程和状态变化。
- 断点设置 :在代码中的关键点设置断点,逐步执行程序,查看变量和寄存器的变化。
- 使用调试器 :利用集成开发环境(IDE)提供的调试工具,进行单步调试、堆栈回溯、变量监视等。
- 系统时钟分析 :检查系统时钟配置,确保系统时钟运行稳定,避免时钟故障导致的问题。
- 资源管理 :确保系统资源(如内存、中断、任务栈等)的合理分配和使用。
常见的错误包括但不限于栈溢出、内存泄漏、竞态条件、优先级翻转等。在分析这些问题时,往往需要结合具体的错误信息、系统行为和代码逻辑,逐步排查和修正。
6. 实验例程的综合应用与测试
6.1 实验平台与工具准备
6.1.1 STM32F103VET6开发板介绍
STM32F103VET6是ST公司的一款高性能微控制器,搭载了ARM Cortex-M3内核,具有丰富的外设接口和较强的处理能力。它支持高达72 MHz的系统时钟频率,提供128 KB的闪存和20 KB的RAM,适合进行复杂的实时应用开发。为了在本实验中充分发挥其性能,我们选择了支持以太网接口的STM32F103VET6开发板,以支持网络功能实验。
6.1.2 调试工具与环境配置
在进行实验前,必须配置合适的开发和调试环境。通常,以下工具是不可或缺的:
- 集成开发环境(IDE) :推荐使用Keil MDK-ARM进行开发和调试,它支持STM32系列并提供了丰富的库函数和插件。
- 编译器 :如上所述,Keil MDK-ARM内置了ARM编译器,用于代码的编译工作。
- 调试器/编程器 :如ST-Link或J-Link,用于下载程序到微控制器,并进行单步调试。
- 软件仿真工具 :如H-Scope或Keil Simulator,用于在没有硬件的情况下测试代码。
确保以上工具安装完成并正确配置后,就可以开始构建实验平台了。首先,将开发板通过USB连接到计算机,然后使用IDE创建一个新项目,并选择STM32F103VET6作为目标MCU。接下来,配置相应的外设驱动和库文件,为编写实验代码做好准备。
6.2 综合实验的设计与实施
6.2.1 UCOS实时任务的调度实验
为了验证UCOS实时任务调度的功能,我们设计了一个包含多个优先级的简单实验。实验中,我们将创建多个任务,并赋予它们不同的优先级。通过任务切换,我们观察到高优先级的任务能够及时抢占CPU,从而验证任务调度机制的正确性。
实验代码中将包含创建任务的代码片段,如:
#define STACK_SIZE 128 // 定义堆栈大小
#define TASK_PRIO_1 3 // 定义任务1的优先级
#define TASK_PRIO_2 2 // 定义任务2的优先级
#define TASK_PRIO_3 1 // 定义任务3的优先级
void Task1(void *pvParameters); // 声明任务1
void Task2(void *pvParameters); // 声明任务2
void Task3(void *pvParameters); // 声明任务3
// 任务1的实现代码
void Task1(void *pvParameters) {
for (;;) {
// 任务执行的代码
}
}
// 任务2的实现代码
void Task2(void *pvParameters) {
for (;;) {
// 任务执行的代码
}
}
// 任务3的实现代码
void Task3(void *pvParameters) {
for (;;) {
// 任务执行的代码
}
}
int main(void) {
// 系统初始化代码
// ...
// 创建任务1、2、3
OSTaskCreate(Task1, NULL, (OS_STK *)&Task1Stk[TASK_PRIO_1], TASK_PRIO_1);
OSTaskCreate(Task2, NULL, (OS_STK *)&Task2Stk[TASK_PRIO_2], TASK_PRIO_2);
OSTaskCreate(Task3, NULL, (OS_STK *)&Task3Stk[TASK_PRIO_3], TASK_PRIO_3);
// 启动操作系统调度
OSStart();
}
6.2.2 CAN通信模块功能测试
CAN通信模块的功能测试涉及到STM32F103VET6的CAN模块配置,以及报文的发送和接收。实验中,我们配置了CAN模块并发送预定的数据帧,然后在另一端接收并验证数据的完整性。
CAN初始化和发送的代码片段可能如下所示:
// CAN初始化代码
void CAN_Config(void) {
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterConfig();
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_4tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 4;
CAN_Init(CAN1, &CAN_InitStructure);
}
// 发送CAN报文
void CAN_SendMessage(void) {
CanTxMsg TxMessage;
TxMessage.StdId = 0x321;
TxMessage.ExtId = 0x01;
TxMessage.RTR = CAN_RTR_DATA;
TxMessage.IDE = CAN_ID_STD;
TxMessage.DLC = 8;
TxMessage.Data[0] = 0x11;
TxMessage.Data[1] = 0x22;
TxMessage.Data[2] = 0x33;
TxMessage.Data[3] = 0x44;
TxMessage.Data[4] = 0x55;
TxMessage.Data[5] = 0x66;
TxMessage.Data[6] = 0x77;
TxMessage.Data[7] = 0x88;
CAN_Transmit(CAN1, &TxMessage);
}
6.2.3 ucGUI界面交互实验
在ucGUI界面交互实验中,我们将展示如何使用ucGUI库创建一个用户交互界面。实验中,我们将设计一个简单的菜单界面,允许用户通过按钮进行交互,并在LCD上显示选择结果。
界面代码可能涉及创建按钮和窗口,代码示例如下:
// 创建一个按钮
static void _CreateButton(GUI pioneering_button, int x0, int y0, int xSize, int ySize) {
GUI_CreateButtonEx(x0, y0, xSize, ySize, pioneering_button.pText, pioneering_button.pacText, pioneering_button.PressAction);
}
// 在LCD上创建菜单界面
void CreateMenuScreen(GUI pioneering_menu) {
GUI_Clear();
GUI_SetColor(GUI_WHITE);
GUI_SetBkColor(GUI_BLACK);
GUI_SetFont(&GUI_Font24_ASCII);
GUI_DispStringHCenterAt("Menu Screen", LCD_GetXSize() / 2, 20);
// 假设存在一个结构体指针数组 pioneering_menu.buttons
for (int i = 0; i < pioneering_menu.buttonCount; i++) {
_CreateButton(pioneering_menu.buttons[i], (LCD_GetXSize() - pioneering_menu.buttons[i].xSize) / 2, pioneering_menu.buttons[i].y0, pioneering_menu.buttons[i].xSize, pioneering_menu.buttons[i].ySize);
}
}
int main(void) {
// 系统初始化代码
// ...
// 创建菜单界面
CreateMenuScreen(pioneering_menu);
while (1) {
// 循环处理GUI消息队列
GUI Animator();
}
}
6.3 实验结果分析与评估
6.3.1 实验数据记录与分析
实验完成后,需要记录实验过程中的关键数据和事件。例如,在实时任务调度实验中,记录任务切换的时间点和执行时间;在CAN通信功能测试中,记录发送和接收消息的时序信息;在ucGUI界面交互实验中,记录用户的输入操作和响应时间。
6.3.2 系统稳定性与性能评估
通过实验记录的数据,我们可以评估系统的稳定性和性能。例如,任务的切换是否满足实时性要求,CAN通信的时延是否符合预期,ucGUI界面的响应是否流畅等。
6.3.3 改进方向与后续工作展望
实验中遇到的问题和不足之处将作为未来改进的方向。例如,若实时任务调度不够高效,可以考虑优化任务优先级分配策略。在CAN通信中,若存在丢包现象,则需检查网络拓扑和配置。对于ucGUI界面,若界面更新不够流畅,可以考虑优化消息处理机制或使用硬件加速。
此章节提供了实验平台的搭建、实验设计与实施的详细步骤,并对实验结果进行了分析与评估。通过以上内容,读者应该能够理解如何在STM32F103VET6平台上开展实际的硬件和软件综合实验,并根据实验结果进行系统性能的评估和优化。
简介:STM32F103VET6是一款基于ARM Cortex-M3内核的微控制器,适用于嵌入式系统设计。本实验例程将指导开发者在STM32F103VET6上运行实时操作系统UCOS,实现CAN通信和图形用户界面ucGUI。学习内容包括微控制器初始化、任务调度、信号量、互斥锁、邮箱、消息队列等实时操作系统功能,以及如何配置CAN通信模块和设计交互式的图形用户界面。此例程包含完整的源代码文件,如main.c、can.c/h、ucos_config.h等,帮助学习者深入理解STM32F103VET6的操作、UCOS的原理以及CAN通信和图形界面的设计与实现。