0%

使用CubeIDE配置JLink和项目

配置Cube

  • image-20211220210102796

  • 新建项目,然后在Cube中配置相关的内容

  • 选择填写相关的信息,名称等

  • image-20211220210224776

  • 此处放大窗口

  • image-20211220211956239

  • 设置好之后,右键创建代码

  • image-20211220212713729

  • 假如此时不能点击generate code的话,右键选择build project然后在提示创建代码的时候选择ok,或者是点击cancel然后再次右键,可以看到generate code可以使用了

  • image-20211220210941718

  • 然后点击这里

  • image-20211220213008029

  • 然后选择

  • image-20211220213904526

  • 此处选择

  • image-20211220215318321

  • 否则会说program file does not exist的错误

  • 注意include 文件的时候需要包括上一级目录到系统的include路径下才行,比如

  • image-20211220215431799

使用freeRTOS的队列和定时器

Cube

  • 注意必须用CMSIS_V2系统才可以设置定时器

image-20211218130310454

image-20211218130352028

然后在freeRTOS.c中的初始化函数MX_FREERTOS_Init中添加启动定时器的语句

void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */

/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */

/* Create the timer(s) */
/* creation of myTimer01 */
myTimer01Handle = osTimerNew(refreshScreen, osTimerPeriodic, NULL, &myTimer01_attributes);

/* creation of myTimer02 */
myTimer02Handle = osTimerNew(drawString, osTimerPeriodic, NULL, &myTimer02_attributes);

/* USER CODE BEGIN RTOS_TIMERS */
osTimerStart(myTimer01Handle, 500);
osTimerStart(myTimer02Handle, 500);
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */

/* Create the queue(s) */
/* creation of colorQ */
colorQHandle = osMessageQueueNew (16, sizeof(uint8_t), &colorQ_attributes);

/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */

/* Create the thread(s) */
/* creation of drawColor */
drawColorHandle = osThreadNew(StartDefaultTask, NULL, &drawColor_attributes);

/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */

/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */

}
  • 队列从Cube里直接创建即可,不需要额外的代码启动。
  • 队列相关的操作函数在cmsis_os2.h中,如图
  • image-20211218130544220

测试CMSIS_V2使用ili9341驱动

  • 能开始像芯片发送一次左右的指令,然后就会不知道因为什么原因(猜测是SPI)进入hardFault无限循环

  • image-20211218132008015

  • 认为还是只能用CMSIS_V1进行操作。

  • 尝试过SPI和DMA都不使用freeRTOS的中断,中断都比RTOS高,也尝试过DMA不用而SPI用,都不行。

解决C语言变量被重复定义错误的问题

头文件编译的原理

  • 在每次头文件被某个.c文件include的时候,头文件头会被插入到代码中#include "xxx.h"的位置,假如在头文件中定义了变量,然后这个头文件被多个.c文件同时Include的话,就会导致头文件中的变量以同样的名称被定义多次。因此产生错误

解决方法

  • 就是在定义该变量的.c文件的头文件中(或者需要用到在其他.c文件中定义的变量的文件中)给对应的变量加extern关键字定义,此时会意味着这个变量是在其他位置被定义的,在此处仅仅是声明一下,而不是定义,也不分配内存空间。此时在该文件被其他.c文件Include的时候,就不会使得变量被定义多次。
  • 或者使用#ifndef#define XX#endif防止if块中的内容被定义多次。

其他知识点

  • C语言中两种方式使用#include命令, #include <>#include ””的区别

  • 一种是在包含指令#include后面”<>”将头文件名括起来。这种方式用于标准或系统提供的头文件,到保存系统标准头文件的位置查找头文件。

  • 另一种是在包含指令#include后用双引号””将头文件包括起来。这种方式常用与程序员自己的头文件。用这种格式时,C编译器先查找当前目录是否有指定名称的头文件,然后在从标准头文件目录中查找。

使用FreeRTOS和上一篇中提到的ili9341芯片驱动点亮屏幕

CubeMX配置

  • SPI:

  • image-20211217213656004

  • DMA:

  • image-20211217213717712

  • NVIC(重要):

  • image-20211217213754825

  • 注意此处SPI、TIM1和DMA不要使用FreeRTOS 的中断函数,将对勾去掉,同时将中断优先级改为一个比FreeRTOS小的数字,比如2。假如使用了FreeRTOS的中断的话在Cube中无法修改这个数字为更小的值

  • FreeRTOS:

  • image-20211217213921708

  • 注意此处使用的是v1,尚未测试v2是否可以,其他内容(比如每个线程的堆栈空间之类的没有修改,默认应该是128字节)

程序配置

主程序中include如下

#include "ili9341.h"
#include "ili9341_font.h"
#include "ili9341_gfx.h"

同时添加

ili9341_t *_lcd;
ili9341_text_attr_t fontSend1;
ili9341_text_attr_t fontSend2;
ili9341_t *screenReturn(void)
{
return _lcd;
}

freeRTOS的C文件中添加上面的Include,此外还有

extern ili9341_text_attr_t fontSend1;
extern ili9341_text_attr_t fontSend2;

freeRTOS的线程定义如下

/* USER CODE END Header_startScreenFill */
void startScreenFill(void const * argument)
{
/* USER CODE BEGIN startScreenFill */
/* Infinite loop */
for(;;)
{
ili9341_fill_screen(screenReturn(),ILI9341_BLUE);
osDelay(500);
}
/* USER CODE END startScreenFill */
}
  • 注意不同的线程要区分优先级,如图
  • image-20211217220230058

文中没有提到的按照上一篇博客的操作

效果如图

20211217220332

STM32F4点亮ili9341驱动的屏幕(HAL库)2

地址

Cube配置

按照readme中的配置,注意以下问题

  • SPI设置(打开中断):
  • image-20211216141849088
  • DMA:
  • image-20211216141832051
  • 生成项目
  • image-20211218120959770

编译注意事项

  • 注意在Flash中将库的目录添加到C/C++目录下

  • image-20211216142551872

  • image-20211216142622319

  • ili9341.h文件添加#pragma anon_unions允许union类型

  • 在keil编译配置管理的c/c++选项卡中Misc control中填写--gnu

  • image-20211216141659806

  • 可能会出现未定义MX_SPI1_Init();的问题,在main.c文件中手动添加dma.h的include

  • lcd初始化示例(执行这个函数即可),返回一个ili9341_t *对象

_lcd = ili9341_new(
&hspi1,
GPIOC, GPIO_PIN_6,
GPIOC, GPIO_PIN_7,
GPIOC, GPIO_PIN_8,
isoLandscape,
GPIOD, GPIO_PIN_15,
GPIOD, GPIO_PIN_15,
itsNONE,
itnNONE);
  • 注意,不使用触摸功能的时候后面几个输入填写任意不用的引脚即可,最后两个写***NONE,有枚举类型的定义
  • 不需要手动执行init之类的函数,之前那个_new中已经包括了

速度比上一个还是快一些

STM32F407点亮ILI9341驱动的屏幕

驱动网址

(国内镜像)

https://hub.fastgit.org/afiskon/stm32-ili9341

接线

  • 屏幕的供电是3.3V
  • SCL是时钟,接SCK
  • SDA是输入信号,接MOSI
  • 其他接对应的输出引脚即可

Cube配置

  • SPI设置(不需要设置为全双工 ,只需要设置为纯发送即可)

image-20211215215305442

  • 但是STM32F4使用HAL库配置的时候的结果与github上的驱动效果略有不同,也就是波特率位42.0MBits/s
  • image-20211215201627479

项目配置

先把ili9341文件夹(在src文件夹中)复制到项目文件夹下

image-20211215202205389

然后在此处将其添加到paths

image-20211215202351571

然后通过add existing files to group… 添加ili9341目录下的文件

  • 然后修改ili9341的头文件

  • image-20211215215407292

  • 还有

  • image-20211215215423986

  • 然后在主程序中include相关的头文件

    #include "ili9341.h"
    #include "fonts.h"

粗略写程序如下

{
/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
ILI9341_Init();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
ILI9341_FillScreen(ILI9341_BLUE);
ILI9341_WriteString(0,10,"Hello World!", Font_7x10, ILI9341_WHITE, ILI9341_BLACK);
HAL_Delay(500);
ILI9341_FillScreen(ILI9341_GREEN);
ILI9341_WriteString(0,10,"Hello World!", Font_7x10, ILI9341_WHITE, ILI9341_BLACK);
HAL_Delay(500);
HAL_UART_Transmit(&huart1,textSend,5,500);
}
/* USER CODE END 3 */
}

但是这个驱动的效果不是特别好,刷新比较慢,会有明显的刷新过程

image-20211215220004575

内存分配的种类

本文转载自https://zhuanlan.zhihu.com/p/115276865

堆栈的关系

image-20211214224037834

​ 如上图的示例程序所示,全局变量和常量属于静态区(Static),由编译器事先分配好,生命周期贯穿整个程序;函数的参数值,局部变量的值属于栈(Stack),由编译器自动分配和释放;程序员用malloc函数动态请求分配的内存空间属于堆(Heap)。值得注意的是,如果在动态分配的内存用完之后忘记使用free函数释放内存,则会导致内存泄漏,并且当堆和栈无止境的增长到互相覆盖对方区域时则会出现很多无法预料的问题。程序可能运行运行着就跑飞了。

FreeRtos 任务空间构成

image-20211214230245382

如上图所示,当调用FreeRTOS的创建任务API函数xTaskCreate()时,FreeRTOS会在堆中开辟出一块空间,用于存放任务的控制信息TCB块栈区Stack用于储存任务相关的变量。图中创建的两个任务Task1和Task2都有各自独立的内存空间,互相独立。如果想在静态区建立任务的话可以调用xTaskCreateStatic()函数。

  • 注意,程序运行过程中动态创建的线程的内存位置在堆中,堆中分配的空间包括程序自身的TCB块和自身的,也就是程序的Stack

TCB块的大小

TCB块的大小取决于FreeRTOSConfig.h头文件中的设置

  • 在最小的设置下TCB块的大小是96字节

  • 如果configUSE_TASK_NOTIFICATIONS是1的话再增加8个字节

  • 如果configUSE_TRACE_FACILITY是1的话再增加8个字节

  • 如果configUSE_MUTEXES是1的话再增加8个字节

上一章节中的任务TCB块因为这三个选项都为1,所以大小为96+8+8+8=120字节。

总结下来 任务占用字节数 = TCB_size + (4 x Task stack size)

TCB块的大小

TCB块的大小取决于FreeRTOSConfig.h头文件中的设置

  • 在最小的设置下TCB块的大小是96字节
  • 如果configUSE_TASK_NOTIFICATIONS是1的话再增加8个字节
  • 如果configUSE_TRACE_FACILITY是1的话再增加8个字节
  • 如果configUSE_MUTEXES是1的话再增加8个字节

上一章节中的任务TCB块因为这三个选项都为1,所以大小为96+8+8+8=120字节。

总结下来 任务占用字节数 = TCB_size + (4 x Task stack size)

MSP和PSP栈指针

在FreeRTOS中维护着两个栈的指针,分别是MSP主堆栈指针(Main stack pointer)和PSP进程堆栈指针(Process stack pointer)。

两个栈指针的区别是

MSP指针

  • 用于操作内核以及处理异常和中断
  • 由编译器分配

PSP指针

  • 用于每个任务的独立的栈指针

  • 在任务调度上下文切换(context switch)中,PSP会初始化为相对应的任务的栈指针,如下图所示

    image-20211214230729141

    通常MSP指针用于系统内核和中断服务函数,PSP指针用于用户的任务。

Heap_1

本方案适用于小型的嵌入式系统,并且这个系统只能在调度器启动之前创建任务和其它内核对象。内存只需要在程序启动调度器前采用first fit算法对内存进行动态分配,之后任务的内存分配在程序的运行周期中保持不变并且无法被释放。heap_1.c实现了一个基础版本的pvPortMalloc函数,并没有实现vPortFree这个函数。如果系统运行后不用删除任务或者内核对象就可以采用这个方案。一些不需要动态分配内存的安全相关的系统也可以采用这个方案,因为这个方案是可确定性的(deterministic)所以不会导致内存碎片化。这个方案中堆由一个数组实现,数组的大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义

image-20211214223607533

示例

  • 内存分配示例如下,A表示没有任何任务创建时的内存;B表示一个任务(每个任务有自己的TCB块和栈区)被创建时的内存分配情况;C表示三个任务被创建时的分配情况。
  • image-20211214223641571

Heap_2

这个方案用于保持FreeRTOS的向下兼容性,并不推荐使用。内存管理也由一个数组实现,大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义。它通过一套 优化算法 (best fit algorithm)对内存进行分配,并允许释放内存。Heap_4是Heap_2的功能强化版本。

best fit 算法确保pvPortMalloc函数分配大小最接近所需要字节的内存空间。它会对大的内存块进行分割,但无法合并相连的内存块。Heap_2适用于重复添加和删除相同任务的系统,但这种系统应该十分少见。

实例

  • 内存分配示例如下,A表示有三个任务被创建时的内存分配情况;B表示有一个任务被删除时的分配情况,此时有两个小的内存块空闲出来;C表示另一个相同任务被创建时的分配情况,因为这个对TCB块和栈区大小的要求和之前被删除任务的大小一样,best fit 算法便把之前被释放的内存块分配给它。
  • image-20211214223823014

Heap_3

本方案使用标准库里的malloc和free函数,所以堆的大小由链接器配置决定,不受configTOTAL_HEAP_SIZE大小影响。因为这个方案使用的场景不多,所以在这里不作详细介绍。

实例

image-20211214232509178

可以看到Heap_3使用标准库里的malloc和free函数对任务进行内存分配和释放

Heap_5

这个方案使用类似于Heap_4的内存分配技术,但不同于Heap_4只用一个连续的数组表示堆,Heap_5可以用不同的数组空间对内存进行分配。在本方案要使用vPortDefineHeapRegions这个函数对不同的数组进行申明。

Heap_5 示例

下面这张图定义了三个不同内存空间用于模拟堆。程序如下,首先定义了每个区域的开始地址START_ADDRESS和空间大小SIZE,然后用一个结构体xHeapRegions指向了这些区域,最后使用vPortDefineHeapRegions函数申明堆的空间。

image-20211214234455862

image-20211214234514596

  • 上图为声明空间的举例

内存管理相关函数

  • size_t xPortGetFreeHeapSize( void );

  • 这个函数会返回当前堆中的空闲空间,可以用来优化堆空间大小。比如在系统运行起来后调用xPortGetFreeHeapSize如果返回了3000,就可以把堆大小configTOTAL_HEAP_SIZE设置为3000。

  • size_t xPortGetMinimumEverFreeHeapSize( void );

  • 这个函数会返回在系统运行过程中堆空间的最小空闲空间,如果最小空闲空间很小的话可以考虑提高堆大小configTOTAL_HEAP_SIZE的值。

  • void vApplicationMallocFailedHook( void );

  • 这是一个回调函数,需要用户自己实现。如果配置文件中configUSE_MALLOC_FAILED_HOOK 设置为1的话,当堆分配内存失败时会调用此函数。用户可以在此函数中进行错误处理。

git 常见命令

https://www.liaoxuefeng.com/wiki/896043488029600

一次添加目录下所有文件( 包括gitignore 的文件):

git add . -f

一次添加目录下的所有文件( 不包括gitignore 的文件):

git add .

一次commit 所有东西

git commit -a -m <信息>

一次添加目录下的所有东西:

git add 文件夹名/*

上传出问题的时候(上传 被拒绝 的时候)的解决方法:

git push -u origin master -f

这个会 丢弃 所有远程仓库领先于本地仓库的部分, 强行 将本地仓库的内容推送到远端

或下面这个,但是这个可能会 覆盖一些本地的文件 (主要是多人开发的情况下可能会出现)

git pull origin master --allow-unrelated-histories

FreeRtos创建注意(使用CubeMX)

  • 创建按照前篇知乎上的文章创建即可,注意此时会提示Systick的问题,设置为外部时钟晶振,而且设置为TIM1(或者其他timer可能也可以,没有尝试过)

  • image-20211214001535585

  • 然后另一个可能存在的问题是在freertos.c 文件中定义一些与UART相关的东西的时候会因为没有#include "usart.h"而出错,此时加上引用即可

资源连接集合

为什么选择FreeRtos?

  • RTOS 类系统有很多,比如 uC/OS,资料很多,尤其是中文资料,那为什么要选择 FreeRTOS 呢?

  • FreeRTOS是免费的,学习RTOS操作系统的话 uC/OS是首选,但要做产品的话,免费的FreeRTOS操作系统就是个不错的选择。

  • 许多半导体厂商产品的 SDK(Software Development Kit—软件开发工具包) 包就使用 FreeRTOS 作为其操作系统,尤其是 WIFI、蓝牙这些带协议栈的芯片或模块。

  • 简单,因为FreeRTOS 的文件数量很少。

FreeRtos 特点

  • FreeRTOS 的内核支持抢占式,合作式和时间片调度。
  • 提供了一个用于低功耗的 Tickless 模式。
  • 系统的组件在创建时可以选择动态或者静态的 RAM,比如任务、消息队列、信号量、软件定时器等等。
  • FreeRTOS-MPU 支持 Corex-M 系列中的 MPU 单元,如 STM32F429。
  • FreeRTOS 系统简单、小巧、易用,通常情况下内核占用 4k-9k 字节的空间。
  • 高可移植性,代码主要 C 语言编写。
  • 高效的软件定时器。
  • 强大的跟踪执行功能。
  • 堆栈溢出检测功能。
  • 任务数量不限。
  • 任务优先级不限。

官网:www.freertos.org

基于STM32F407和Cubemx(HAL库)的FreeRtos开发

待续