stm32l151上移植rt-thread nano笔记

发布于 2019-10-25 17:31:58
[md]

## 1.简介

对于stm32l151来说,首先需要将系统环境搭建起来,所以首先RT-Thread nano。该芯片占用的资源有限,所以移植一个完整的rt_thread,比较耗费资源。所以优先选择RT-Thread nano。在支持semaphore和mailbox特性,并运行两个线程(main线程+idle线程)情况下,ROM和RAM依然保持着极小的尺寸。基于Cortex M0 MCU的一个例子,编译后的大小(ROM: 3.25K, RAM: 1.04K),除去MCU需要的ROM和RAM, RT-Thread Nano本身需要的ROM是2.5K , RAM 是1K。本文主要从移植的角度出发,分析系统的构建过程。

## 2. 操作流程

### 2.1 keil中安装rtthread包

首先安装rtthread的包,在MDK5中选择**Pack Istaller**
![]()
接着安装运行环境
![]()
选择rtthread内核
![]()
选择完成后,就可以看到工程中多了如下的目录
![]()
其中文件的含义如下
![]()

### 2.2 函数接口对接

RT-Thread会用到了异常处理函数`HardFault_Handler()`和悬挂处理函数`PendSV_Handler()`,以及Systick中断服务函数`SysTick_Handler()`,所以用户代码需要保证这几个函数没有被使用,若编译提示函数重复定义,请删除自己定义的函数。
![]()
用户只需要屏蔽掉自己写的函数即可。也可以采用弱函数的方式

```
__weak void SysTick_Handler(void)
__weak void PendSV_Handler(void)
__weak void HardFault_Handler(void)
```

### 2.3 rtthread系统配置

点击rtconfig.h可以进入系统配置界面
![]()
选择必要的配置
![]()
![]()
这样一个rtt工程项目就搭建完成了。

## 3. 启动流程

### 3.1 引导过程

![]()
_ _main 能跳转到`int $Sub$$main(void); `然后`$Super$$main();`能跳转到我们正常的main()函数中。
其中有关`$Super$` and `$Sub$` 是mdk的一种链接方式,具体用法可以看
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html
![]()
然后进入rtthread_startup函数中
![]()
其中在初始化堆栈是,初始的堆栈空间为4k
![]()
在创建main线程时,可以看到创建的main函数执行入口。
![]()
main函数的优先级为2,占用的堆空间为512字节。

### 3.2 驱动初始化

对于HAL库函数,主要的初始化是对基本的HAL库函数的初始化。
![]()
这里,主要对几个驱动进行一下完善:

#### 3.2.1 gpio位带操作

单片机操作GPIO口的时候,只用将对应的io口设置为1或者0即可,这样的好处就是直接通过操作一个字来达到控制位的作用。
![]()
![]()
位带操作的两个区域
0x2000_0000-0x200F_FFFF(SRAM区中最低的1M)
0x4000_0000-0x400F_FFFF(片上外设最低的1M)
对于SRAM位带操作中,若要操作某个比特
那么只需要操作的地址
AliasAddr=0x22000000+((A-0x20000000)\*8+n)\*4=0x22000000+32\*(A-0x20000000)+4\*n
做如下的处理

```c
//位带操作
//具体实现思想,参考<>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
```
#### 3.2.3 timr6做外部定时器

只需要在使用定时器时调用`HAL_TIM_Base_Start(&htim6);`开启定时器,并且在函数中调用`HAL_TIM_Base_Stop(&htim6);`关闭定时器。
所以实现us级别延时函数的代码如下所示:

```c
//采用定时器6实现一个us级别的延时函数
void delay_us(uint16_t us)
{
uint16_t differ=0xffff-us-5;
/*为防止因中断打断延时,造成计数错误.
如从0xfffE开始延时1us,但由于中断打断
(此时计数器仍在计数),本因计数至0xffff)
便停止计数,但由于错过计数值,并重载arr值,
导致实际延时(0xffff+1)us
*/
HAL_TIM_Base_Start(&htim6);
__HAL_TIM_SetCounter(&htim6,differ);
while(differ<0xffff-5)
{
differ=__HAL_TIM_GetCounter(&htim6);
}
HAL_TIM_Base_Stop(&htim6);
}
```

#### 3.2.4 wwdg窗口看门狗

窗口看门狗的使用主要是配置与使用,如果由于系统跑飞,应该启动看门狗,让系统复位。在这里,可采用在rtthread的tick中断中喂狗的方式,如果系统程序执行遇到异常,将启动看门狗,让系统复位。
所以在使用看门狗时,首先需初始化`MX_WWDG_Init();`,然后在一定的时间内进行喂狗即可。
在使用休眠模式模式时,该寄存器的值不能自动复位,所以在复位之前可以喂一次狗,在从休眠模式到程序正常运行时,也需要喂狗一次。
根据计算公式:
$$
4096*8*64/2.097\approx1000000
$$
可以得到超时时间为1s,也就是1000ms。而在rtthread的tick中可以设置10ms喂狗一次也可以设置1ms喂狗一次。
喂狗函数:

```c
HAL_WWDG_Refresh(hwwdg, WWDG_CNT);
```

#### 3.2.5 uart接收与发送

可采用dma不定长接收的方式进行处理,也可以采用循环队列的方式,具体情况需要看使用时的具体使用方式。这部分后面单独提出来进行分析。

#### 3.2.6 低功耗运行模式

由于在stop模式下,很多外设都在工作,所以在进入stop模式之前,需要做如下的几件事

- 降低核心电压
- 禁用比较器
- 禁止PVD
- 关闭VREFINT(少3uA)
- 忽略VREFINT,加快启动速度
- 禁用调试端口

#### 3.2.7 进入stop模式

进入stop模式的代码如下:

```c
void power_enter_stop(void)
{
GPIO_InitTypeDef GPIO_InitStruct={0};
printf("=== Power Down ===\r\n");
LED_OFF;
HAL_UART_MspDeInit(&huart1);
//UART1; 减少16uA
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_VERY_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__GPIOA_CLK_DISABLE();
__GPIOB_CLK_DISABLE();
__GPIOC_CLK_DISABLE();
__GPIOH_CLK_DISABLE();
/* Enter Stop Mode */
// __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
// /* Poll VOSF bit of in PWR_CSR. Wait until it is reset to 0 */
// while (__HAL_PWR_GET_FLAG(PWR_FLAG_VOS) != RESET) {};
// PWR_LOWPOWERREGULATOR_ON 少2uA
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
```

注意的是,在唤醒后,需要重新配置串口,重新配置GPIO时钟以及清除唤醒标记。‘

```c
void power_exit_stop(void)
{
SystemClock_Config();
__GPIOA_CLK_ENABLE();
__GPIOB_CLK_ENABLE();
// __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
// /* Poll VOSF bit of in PWR_CSR. Wait until it is reset to 0 */
// while (__HAL_PWR_GET_FLAG(PWR_FLAG_VOS) != RESET) {};
// 清除唤醒标记 减少重复唤醒电流,减少3uA
SET_BIT(PWR->CR, PWR_CR_CWUF);
//重新初始化串口
HAL_UART_MspInit(&huart1);
}
```

## 4. 总结

目前可以采用rtthread nano进行相关的程序框架设计。对于内存比较小的芯片,使用nano版本将是一个十分不错的选择。[/md]

查看更多

关注者
0
被浏览
1.2k
2 个回答

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览