NBDK-L4:LiteOS实验教程

来自谷雨文档中心
Jinx讨论 | 贡献2019年4月4日 (四) 10:48的版本 stm32l4xx_hal_conf.h
跳转至: 导航搜索

1 教程介绍

经过NBDK-L4基础实验部分的学习,大家应该已经能够熟练的使用STM32Cube生成工程,并且掌握工程各部分文件的功能,完成个人需求的开发。在开发的过程中,我们不难发现,所有的功能都需要在main()函数中去配置工作,这样的工作方式很明显不能适用于较复杂的功能开发,所以我们需要引入OS操作系统,以此来方便我们的开发。本章节我们将给大家带来华为LiteOS操作系统的使用和讲解。

对于LiteOS的学习和使用,我们主要分为三个步骤:

1.了解LiteOS

在华为的IOT开发者专区中,有专门对于LiteOS的讲解,大家可以通过如下的链接前往查看

华为IOT开发者专区:https://developer.huaweicloud.com/iot

华为LiteOS源码及说明:https://github.com/LiteOS/LiteOS

2.如何在Cube生成的工程中移植LiteOS

在华为LiteOS源码及说明的链接中,有给大家说明如何移植华为LiteOS。

因为LiteOS是持续更新的,所以移植起来不是特别方便(目录架构总在变),我们建议大家可以先略过这一步骤,直接使用我们移植好的工程去进行需求开发。

3.如何使用LiteOS开发我们的功能

这一部分是我们LiteOS实验的主体部分,请大家跟随我们的实验一起学习。

Icon-info.png
Huawei LiteOS是华为面向物联网领域开发的一个基于实时内核的轻量级操作系统。本项目属于华为物联网操作系统Huawei LiteOS源码,现有基础内核支持任务管理、内存管理、时间管理、通信机制、中断管理、队列管理、事件管理、定时器等操作系统基础组件,更好地支持低功耗场景,支持tickless机制,支持定时器对齐。

同时提供端云协同能力,集成了LwM2M、CoAP、mbedtls、LwIP全套IoT互联协议栈,且在LwM2M的基础上,提供了AgentTiny模块,用户只需关注自身的应用,而不必关注LwM2M实现细节,直接使用AgentTiny封装的接口即可简单快速实现与云平台安全可靠的连接。

Huawei LiteOS自开源社区发布以来,围绕NB-IoT物联网市场从技术、生态、解决方案、商用支持等多维度使能合作伙伴,构建开源的物联网生态,目前已经聚合了30+ MCU和解决方案合作伙伴,共同推出一批开源开发套件和行业解决方案,帮助众多行业客户快速的推出物联网终端和服务,客户涵盖抄表、停车、路灯、环保、共享单车、物流等众多行业,为开发者提供 “一站式” 完整软件平台,有效降低开发门槛、缩短开发周期。

1.1 工程简介

1.2 工程目录简介

大家打开任意一个LiteOS例程,都会看到如下的4个目录(Drivers、LiteOS、MDK-ARM、Src)及clean.bat文件。

其中clean.bat是用于清除工程编译生成的中间文件。例如我们想拷贝一个编译过的工程,工程有200M左右大小,我们点击clean.bat清除一下编译生成的中间文件,则工程大概会缩小到100M左右,此时工程只剩下了库文件、用户文件,以及编译生成的hex文件。

NBDK-LOS-TAB-MPath.png

从上图可以看到,四个主目录下分别包含的一些文件,这边给大家简单的介绍一下这边文件大概的功能。

Drivers:

STM32驱动文件目录,也就是大家常说的hal库,里面包含了hal(硬件抽象层)相关的文件。

主要就是有RCC时钟、Flash内存,以及大家常用的外设(例如uart、spi、adc等等)的一些库文件。

LiteOS:

华为LiteOS操作系统目录,里面包含了两个目录:arch、kernel。

这两个目录就是我们移植到STM32cube工程中的LiteOS相关文件。

MDK-ARM:

工程目录,主要是两个工程文件“.uvoptx”以及".uvprojx"(keil打开的是这个)。剩下的文件比较重要的是Output目录下编译生成的“.hex”文件。

Src:

用户文件,用户自己开发的一些驱动文件(外设驱动等等),以及main文件所在的目录。

1.3 实验01-TFT显示屏

LiteOS的第一个实验,我们给大家带来的是LCD图形化显示实验。至于我们为什么要首先讲解LCD相关的例程,而不是最简单的LED控制实验,主要目的有三个:

1.大家了解了LCD例程之后,对于后面的其他实验,可以使用LCD显示一些调试信息,这样便利于大家的开发。

2.我们会借由此例程,给大家讲解LiteOS例程在Keil5中的目录结构,以及我们开发者需要关注的重要文件及重要功能函数。

3.借由此例程,给大家展示keil5 options中的配置。

1.4 工程文件说明

这一章节我们给大家介绍一下,每个工程的Group目录以及其下包含的文件的功能。

NBDK-LOS-KEIL-PRO.png

KEIL工程文件简介
Group file 说明
LiteOS/arch arch目录取名来源于architecture(建筑结构),所包含的是硬件配置文件
los_dispatch_keil.c keil中los的系统调度文件
los_hw.c hardware硬件配置文件
los_hw_tick.c hardware tick硬件系统tick文件
los_hwi.c hardware interrupt硬件中断文件
LiteOS/kernel los内核
cmsis_liteos.c los提供的CMSIS接口
los_config.c los配置文件,用于初始化并启动los
los_priqueue.c priority queue,los队列优先级处理
los_swtmr.c software timer,los软件定时器
los_sys.c 系统tick
los_task.c 用于创建和处理任务
los_tick.c 仅包含了tick中断函数
los_timeslice.c 时间片初始化和设置
los_event.c 事件
los_mux.c 互斥锁
los_queue.c 队列
los_sem.c 信号量
los_membox.c 内存池初始化、分配、清除
los_memory.c 内存节点申请、清理、优化处理
los_memstat.c 内存状态
los_muitipledlinkhead.c 多内存处理
los_misc.c 包含了两个功能函数,字节对齐、当前任务休眠
Driver/STM32L4xx_hal_driver 和硬件相关的HAL抽象层文件,主要是电源、flash、外设。HAL不清楚如何使用的,大家可以先看下基础实验部分。
Driver/CMSIS Cortex-M 处理器系列的与供应商无关的硬件抽象层
system_stm32l4xx.c STM32L4系列硬件抽象层文件
startup 系统启动文件
los_startup_keil.s los在keil中的启动项文件
Application/Display TFT彩屏驱动,以及GUI图形界面驱动文件
gui.c GUI图形界面文件
gui_dispstr.c GUI字符串打印
gui_dispval.c GUI值打印
gui_drawBitmap.c GUI图形打印
gui_os.c GUI操作系统任务
lcd.c TFT驱动文件
bmp_keyio.c 按键图形界面
bmp_logo.c 谷雨logo图形界面
Font8x16.c 描述8*16大小的常见英文字符
FontMethod.c 定义英文字体的通用函数文件
FontZH16x16_simsun.c 描述16*16大小的中文字符
FontZHMethod.c 定义中文字体的通用函数文件
lcddrv_tft_130 TFT屏驱动文件,1.3型号
lcddrv_tft_144 TFT屏驱动文件,1.44型号
lcd_gpio.c TFT屏BL、CD、CS引脚控制
lcd_hw_cfg.c TFT屏硬件接口定义
lcd_spi.c TFT屏SPI接口控制
Application/bsp Board Support Package 板级支持包,结合STM32L4平台的外设驱动
los_bsp_adapter.c LOS和STM32的适配文件
los_bsp_lcd.c LCD驱动文件,结合STM32硬件
Application/task 用户任务,用户个人创建的task
Application/user 用户文件,main函数、系统时钟、系统外设宏定义
stm32l4xx_it.c STM32中断函数文件
sys_init.c STM32系统时钟以及外设时钟定义文件
main.c 用于初始化los并运行,并且初始化和定义用户任务

1.5 实验验证

编译并下载此例程,由于涉及的库文件较多,编译将持续1min以上,请大家耐心等待。

下载完成后,可以看到TFT显示屏打印“LiteOS”、"01_los_lcd"字样。

1.6 源码详解

源码详解部分,第一个这个LCD部分,我们会按照基础实验部分的源码详解方式说明,后续的其他实验,我们仅讲解相对此例程新增的功能部分,所以请大家一定仔细阅读此例程说明。

1.6.1 stm32l4xx_hal_conf.h

此文件位于“实验01-TFT显示屏\Src\User”路径中,主要用途是选择使能此例程使用到的HAL库文件。

使用STM32,大家默认需要打开的几个宏定义,芯片使能、flash、电源、时钟、NVIC,这个也很好理解,就是让芯片工作的必要几点。

此例程我们给大家展示的是TFT显示屏,所以我们需要请打开驱动TFT工作的SPI、GPIO以及DMA三个宏。其他的外设例程也需要使能对应的宏,这个大家有使用的疑问,可以查找一下基础实验中的相应的外设例程。

103 // 使能的宏
104 #define HAL_MODULE_ENABLED          // 芯片
105 #define HAL_FLASH_MODULE_ENABLED    // Flash
106 #define HAL_PWR_MODULE_ENABLED      // 电源
107 #define HAL_RCC_MODULE_ENABLED      // 时钟
108 #define HAL_CORTEX_MODULE_ENABLED   // NVIC
109 
110 #define HAL_GPIO_MODULE_ENABLED     // GPIO
111 #define HAL_DMA_MODULE_ENABLED      // DMA
112 #define HAL_SPI_MODULE_ENABLED      // SPI

1.6.2 sys_init.c

时钟初始化函数,用于配置我们模块运行的系统时钟、AHB高性能总线时钟、APB外设总线时钟以及单个外设的时钟。

主要包含了三个部分的初始化配置。

  1. 内部或者外部振荡器选择,也就是选择时钟信号的来源,是内部振荡,还是外部晶振。
  2. 时钟配置,选择系统、AHB总线及APB总线的时钟来源。
  3. 外设时钟配置,选择外设时钟来源。

为了给大家比较全面的展示各个时钟,我们振荡器选择HSI(内部16MHz高频)、HSE(外部8MHz高频)以及LSE(外部32.768KHz低频)三个。选择HSE作为PLL(锁相回路)时钟源,配置PLLCLK为80MHz。配置系统时钟SYSCLK、AHB高性能总线、APB外设总线(APB1及APB2)为80MHz。另外我们还分别配置了ADC、UART以及I2C的外设时钟。

基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。

 41 //******************************************************************
 42 // fn : SystemClock_Config
 43 //
 44 // brief : 系统时钟配置函数
 45 //
 46 // param : none
 47 //
 48 // return : none
 49 void SystemClock_Config(void)
 50 {
 51   RCC_OscInitTypeDef RCC_OscInitStruct;     // 定义RCC内部/外部振荡器结构体
 52   RCC_ClkInitTypeDef RCC_ClkInitStruct;     // 定义RCC系统,AHB和APB总线时钟配置结构体
 53   RCC_PeriphCLKInitTypeDef PeriphClkInit;   // 定义RCC扩展时钟结构体
 54   
 55   // 配置LSE驱动器功能为低驱动能力
 56   __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
 57 
 58   // 初始化CPU,AHB和APB总线时钟
 59   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE
 60                               |RCC_OSCILLATORTYPE_LSE;        // 设置需要配置的振荡器为HSI、HSE、LSE
 61   // 配置HSE
 62   RCC_OscInitStruct.HSEState = RCC_HSE_ON;                    // 激活HSE时钟(开发板外部为8MHz)
 63   // 配置LSE
 64   RCC_OscInitStruct.LSEState = RCC_LSE_ON;                    // 激活LSE时钟(32.768KHz,低驱动)
 65   // 配置HSI
 66   RCC_OscInitStruct.HSIState = RCC_HSI_ON;                    // 激活HSI时钟
 67   RCC_OscInitStruct.HSICalibrationValue = 16;                 // 配置HSI为16MHz     
 68   // 配置PLL
 69   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                // 打开PLL
 70   RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;        // 选择HSE时钟作为PLL入口时钟源,8MHz
 71   RCC_OscInitStruct.PLL.PLLM = 1;                             // 配置PLL VCO输入分频为1,8/1 = 8MHz
 72   RCC_OscInitStruct.PLL.PLLN = 20;                            // 配置PLL VCO输入倍增为20,8MHz*20 = 160MHz
 73   RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;                 // SAI时钟7分频,160/7 = 22.857143MHz
 74   RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;                 // SDMMC、RNG、USB时钟2分频,160/2 = 80MHz
 75   RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;                 // 系统主时钟分区2分频,160/2 = 80MHz
 76   // RCC时钟配置,出错则进入错误处理函数
 77   if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
 78   {
 79     _Error_Handler(__FILE__, __LINE__);
 80   }
 81   
 82   // 初始化CPU,AHB和APB总线时钟
 83   RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
 84                               |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // 需要配置的时钟HCLK、SYSCLK、PCLK1、PCLK2
 85   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;             // 配置系统时钟为PLLCLK输入,80MHz
 86   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                    // AHB时钟为系统时钟1分频,80/1 = 80MHz
 87   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                     // APB1时钟为系统时钟1分频,80/1 = 80MHz
 88   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                     // APB2时钟为系统时钟1分频,80/1 = 80MHz
 89   // RCC时钟配置,出错则进入错误处理函数
 90   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // HCLK=80MHz,Vcore=3.3V,所以选择SW4(FLASH_LATENCY_4)
 91    {
 92     _Error_Handler(__FILE__, __LINE__);
 93   }
 94   
 95   // 初始化外设时钟
 96   PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_USART2
 97                               |RCC_PERIPHCLK_LPUART1|RCC_PERIPHCLK_LPTIM1
 98                               |RCC_PERIPHCLK_I2C2|RCC_PERIPHCLK_ADC;  // 需要初始化的外设时钟:USART1、USART2、LPUART1、LPTIM1、I2C2、ADC
 99   PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;     // 配置串口USART1时钟为PCLK2,80MHz
100   PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;     // 配置串口USART2时钟为PCLK1,80MHz
101   PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI;     // 配置LPUART时钟为HSI,16MHz
102   PeriphClkInit.I2c2ClockSelection = RCC_I2C2CLKSOURCE_PCLK1;         // 配置I2C2时钟为PCLK1,80MHz
103   PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE;       // 配置LPTIM1时钟为LSE,32.768KHz
104   PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;         // 配置ADC时钟为PLLSAI1,现在为80MHz,下面会重新定义
105   PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;            // 配置PLLSAI1时钟为HSE,8MHz
106   PeriphClkInit.PLLSAI1.PLLSAI1M = 1;                                 // 配置PLLSAI1分频为1
107   PeriphClkInit.PLLSAI1.PLLSAI1N = 8;                                 // 配置PLLSAI1倍增为8
108   PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7;                     // SAI时钟7分频,64/7 = 9.142857MHz
109   PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;                     // SDMMC、RNG、USB时钟2分频,64/2 = 32MHz
110   PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;                     // 系统主时钟分区2分频,64/2 = 32MHz             
111   PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK;        // 配置PLLSAI1输出为ADC1时钟,也就是配置ADC1时钟,32MHz
112   // 外设时钟配置,出错则进入错误处理函数
113   if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
114   {
115     _Error_Handler(__FILE__, __LINE__);
116   }
117   
118   // 配置内部主稳压器输出电压,配置为稳压器输出电压范围1模式,也就是:典型输出电压为1.2V,系统频率高达80MHz
119   if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
120   {
121     _Error_Handler(__FILE__, __LINE__);
122   }
123 
124   // 配置系统定时器中断时间,配置为HCLK的千分频
125   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
126 
127   // 配置系统定时器,配置为HCLK
128   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
129 
130   // 系统定时器中断配置,设置系统定时器中断优先级最高(为0),且子优先级最高(为0)
131   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
132 }
133 
134 
135 //******************************************************************
136 // fn : _Error_Handler
137 //
138 // brief : 错误处理程序
139 //
140 // param : none
141 //
142 // return : none
143 void _Error_Handler(char * file, int line)
144 {
145   // 暂时未处理任何事情,用户可用来添加自己的功能来报告HAL错误返回状态
146   while(1) 
147   {
148   }
149 }

1.6.3 main.c

main.c文件中,我们只放置了3个函数,以后的所有例程也只有这3个函数,下面我们将简要说明各个函数的功能。

硬件初始化函数,顾名思义,就是我们初始化硬件驱动的函数,在这个实验中,我们初始化了LCD的驱动文件。

27 void HardWare_Init(void)
28 {
29   // 初始化LCD
30   BSP_EvbLcdInit();
31 }

LiteOS任务初始化函数,我们会在这个函数中初始化我们用户新建的task任务,在这个TFT实验中,我们没有独立的任务,仅仅初始化了GUI图形化界面。

41 void LosTask_Init(void)
42 {
43   // 初始化图形接口
44   GUI_Init();    
45   GUI_DispString("LiteOS");
46   GUI_DispNextLine();
47   GUI_DispString("01_los_lcd");
48 }

main()函数中,我们首先初始化HAL和系统时钟,接着我们初始化了LiteOS内核,使能LiteOS系统tick中断,然后我们分别初始化了我们所使用的硬件驱动和los任务,最后我们开始

58 int main(void)
59 {
60   UINT32 uwRet;
61   
62   // 初始化硬件
63   HAL_Init();
64   SystemClock_Config();
65   
66   // 初始化LOS内核
67   uwRet = LOS_KernelInit();
68   if (uwRet != LOS_OK) 
69   {
70     return LOS_NOK;
71   }
72   
73   // 使能LOS系统tick中断
74   uwRet = LOS_EnableTick();
75   if (uwRet != LOS_OK)
76   {
77     return LOS_NOK;
78   }
79   
80   // 初始化开发板硬件
81   HardWare_Init();
82   
83   // 初始化用户任务
84   LosTask_Init();
85   
86   // 运行LOS
87   (void)LOS_Start();
88   for(;;);
89 }

1.7 los_bsp_lcd.c

2 实验02-led点灯

2.1 实验验证

2.2 源码详解

本PDF由谷雨文档中心自动生成,点击下方链接阅读最新内容。

取自“http://doc.iotxx.com/index.php?title=NBDK-L4:LiteOS实验教程&oldid=1690