一、实验要求
1.学习嵌入式实时操作系统(RTOS),以uc/OS-III为例,将其移植到stm32F103上,构建至少3个任务(task):其中两个task分别以1s和3s周期对LED等进行点亮-熄灭的控制;另外一个task以2s周期通过串口发送“hello uc/OS! 欢迎来到RTOS多任务环境!”。记录详细的移植过程。
2.在上述实验中,在掌握Keil的仿真调试代码功能之外,也学习使用仪器对代码运行进行故障排查和功能调测。 (1)练习使用示波器去观察LED输出电平和串口通信的波形,分析故障; (2)分别使用Keil虚拟仿真逻辑仪和真实逻辑仪(SaleaeLogic16)抓取LED输出电平和串口通信的波形,进行协议分析。
二、实验过程及结果
(一)移植uc/OS-III
1. 下载uC/OS-III源码
官网下载:http://micrium.com/downloadcenter/ 云盘下载:https://pan.baidu.com/s/1Btj7foEXdXjjJWoZQsN-OQ 提取码:mleh
2. 建立STM32HAL库
选择STM32F103C8 芯片,修改RCC 中的HSE 为Crystal/Ceramic Resonator
点击System Core 中的SYS ,在Debug 处选择Serial Wire
选择串口USART1 ,点击Mode ,选择Asynchronous
设置引脚PA1 、PB15 为GPIO_Output
点击Clock Configuration ,选择HSE 和PLLCLK ,设置HCLK 为72MHz
设置项目名称和路径,修改Toolchain/IDE 为MDK-ARM ,在Code Generator 中勾选
3. 复制uC/OS-III文件
在生成的keil工程文件夹中新建一个uCOS 文件夹
将下载源文件下的uC-CPU 、uC-LIB 、uCOS-III 复制到此
在Core 中的Src 文件夹下新建一个OS 文件夹
将Micrium\Software\EvalBoards\Micrium\uC-Eval-STM32F107\uCOS-III 下的app.c 、 app_cfg.h 、 cpu_cfg.h 、 includes.h 、 lib_cfg.h 、 os_app_hooks.c 、os_app_hook.h 、os_cfg.h 、os_cfg_app.h 文件复制到OS文件夹
在OS 文件夹中新建三个空白文件 bsp.c 、bsp.h 、app.h
4. 添加工程组件及路径
打开工程, 添加六个新组:bsp 、uCOSIII_CPU 、uCOSIII_LIB 、uCOSIII_Ports 、uCOSIII_Source 、OS_cfg
将app.c 添加进Application/User/Core 组,将Core/Src/OS 中的bsp.c 、bsp.h 添加进bsp 组
将uCOS/uC-CPU 中的cpu_core.c 、cpu_core.h 、cpu_def.h 和uCOS/uC-CPU/ARM-Cortex-M3/RealView 中的cpu.h 、cpu_c.c 、cpu_a.asm 添加进uCOSIII_CPU 组
将uCOS/uC-LIB 中的9个文件和uCOS/uC-LIB/Ports/ARM-Cortex-M3/Realview 中的lib_mem_a.asm 添加到uCOSIII-LIB 组
将uCOS/uCOS-III/Ports/ARM-Cortex-M3/Generic/RealView 中的3个文件添加到uCOSIII_Ports 组
将uCOS/ucos-III/Source 中的20个文件添加到uCOSIII_Source 组
将Core/Src/OS 中的8个文件添加到OS_cfg 组
点击魔法棒 ,选择C/C++ 添加文件路径
点击魔法棒 ,选择Target 配置相关参数
5. 修改文件内容
(1)startup_stm32f103xb.s启动文件
PendSV_Handler -> OS_CPU_PendSVHandler
Systick_Handler -> OS_CPU_SysTickHandler
(2)app_cfg.h头文件
#define APP_CFG_SERIAL_EN DEF_ENABLED
#define APP_TRACE BSP_Ser_Printf
#define APP_CFG_SERIAL_EN DEF_DISABLED
#define APP_TRACE (void)
(3)includes.h头文件
#include "gpio.h"
#include "app_cfg.h"
#include "app.h"
#include <stm32f10x_lib.h>
#include "stm32f1xx_hal.h"
(4)bsp.c、bsp.h文件
bsp.c
#include "includes.h"
#define DWT_CR *(CPU_REG32 *)0xE0001000
#define DWT_CYCCNT *(CPU_REG32 *)0xE0001004
#define DEM_CR *(CPU_REG32 *)0xE000EDFC
#define DBGMCU_CR *(CPU_REG32 *)0xE0042004
#define DEM_CR_TRCENA (1 << 24)
#define DWT_CR_CYCCNTENA (1 << 0)
CPU_INT32U BSP_CPU_ClkFreq (void)
{
return HAL_RCC_GetHCLKFreq();
}
void BSP_Tick_Init(void)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
cpu_clk_freq = BSP_CPU_ClkFreq();
#if(OS_VERSION>=3000u)
cnts = cpu_clk_freq/(CPU_INT32U)OSCfg_TickRate_Hz;
#else
cnts = cpu_clk_freq/(CPU_INT32U)OS_TICKS_PER_SEC;
#endif
OS_CPU_SysTickInit(cnts);
}
void BSP_Init(void)
{
BSP_Tick_Init();
MX_GPIO_Init();
}
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void CPU_TS_TmrInit (void)
{
CPU_INT32U cpu_clk_freq_hz;
DEM_CR |= (CPU_INT32U)DEM_CR_TRCENA;
DWT_CYCCNT = (CPU_INT32U)0u;
DWT_CR |= (CPU_INT32U)DWT_CR_CYCCNTENA;
cpu_clk_freq_hz = BSP_CPU_ClkFreq();
CPU_TS_TmrFreqSet(cpu_clk_freq_hz);
}
#endif
#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
CPU_TS_TMR CPU_TS_TmrRd (void)
{
return ((CPU_TS_TMR)DWT_CYCCNT);
}
#endif
#if (CPU_CFG_TS_32_EN == DEF_ENABLED)
CPU_INT64U CPU_TS32_to_uSec (CPU_TS32 ts_cnts)
{
CPU_INT64U ts_us;
CPU_INT64U fclk_freq;
fclk_freq = BSP_CPU_ClkFreq();
ts_us = ts_cnts / (fclk_freq / DEF_TIME_NBR_uS_PER_SEC);
return (ts_us);
}
#endif
#if (CPU_CFG_TS_64_EN == DEF_ENABLED)
CPU_INT64U CPU_TS64_to_uSec (CPU_TS64 ts_cnts)
{
CPU_INT64U ts_us;
CPU_INT64U fclk_freq;
fclk_freq = BSP_CPU_ClkFreq();
ts_us = ts_cnts / (fclk_freq / DEF_TIME_NBR_uS_PER_SEC);
return (ts_us);
}
#endif
bsp.h
#ifndef __BSP_H__
#define __BSP_H__
#include "stm32f1xx_hal.h"
void BSP_Init(void);
#endif
(5)app.c、app.h文件
app.c
#include "includes.h"
app.h
#ifndef __APP_H__
#define __APP_H__
#include <includes.h>
#endif
(6)lib_cfg.h文件
STM32F103C8 的RAM 总共20K
#define LIB_MEM_CFG_HEAP_SIZE 27u * 1024u
#define LIB_MEM_CFG_HEAP_SIZE 5u * 1024u
(7)usart.c文件
#include "stdio.h"
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
(8)main.c文件
#include "main.h"
#include "gpio.h"
#include "usart.h"
#include <includes.h>
#include "stm32f1xx_hal.h"
#define START_TASK_PRIO 3
#define LED0_TASK_PRIO 4
#define LED1_TASK_PRIO 4
#define MSG_TASK_PRIO 5
#define START_STK_SIZE 64
#define LED0_STK_SIZE 64
#define LED1_STK_SIZE 64
#define MSG_STK_SIZE 64
CPU_STK START_TASK_STK[START_STK_SIZE];
CPU_STK LED0_TASK_STK[LED0_STK_SIZE];
CPU_STK LED1_TASK_STK[LED1_STK_SIZE];
CPU_STK MSG_TASK_STK[MSG_STK_SIZE];
OS_TCB StartTaskTCB;
OS_TCB Led0TaskTCB;
OS_TCB Led1TaskTCB;
OS_TCB MsgTaskTCB;
void start_task(void *p_arg);
static void AppTaskCreate(void);
static void AppObjCreate(void);
static void LED_PA1(void *p_arg);
static void LED_PB15(void *p_arg);
static void send_msg(void *p_arg);
void SystemClock_Config(void);
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
int main(void)
{
OS_ERR err;
OSInit(&err);
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
OSTaskCreate((OS_TCB *)&StartTaskTCB,
(CPU_CHAR *)"start task",
(OS_TASK_PTR ) start_task,
(void *) 0,
(OS_PRIO ) START_TASK_PRIO,
(CPU_STK *)&START_TASK_STK[0],
(CPU_STK_SIZE) START_STK_SIZE/10,
(CPU_STK_SIZE) START_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSStart(&err);
}
void start_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
BSP_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER();
OSTaskCreate((OS_TCB * )&Led0TaskTCB,
(CPU_CHAR * )"LED_PA1",
(OS_TASK_PTR )LED_PA1,
(void * )0,
(OS_PRIO )LED0_TASK_PRIO,
(CPU_STK * )&LED0_TASK_STK[0],
(CPU_STK_SIZE)LED0_STK_SIZE/10,
(CPU_STK_SIZE)LED0_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&Led1TaskTCB,
(CPU_CHAR * )"LED_PB15",
(OS_TASK_PTR )LED_PB15,
(void * )0,
(OS_PRIO )LED1_TASK_PRIO,
(CPU_STK * )&LED1_TASK_STK[0],
(CPU_STK_SIZE)LED1_STK_SIZE/10,
(CPU_STK_SIZE)LED1_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&MsgTaskTCB,
(CPU_CHAR * )"send_msg",
(OS_TASK_PTR )send_msg,
(void * )0,
(OS_PRIO )MSG_TASK_PRIO,
(CPU_STK * )&MSG_TASK_STK[0],
(CPU_STK_SIZE)MSG_STK_SIZE/10,
(CPU_STK_SIZE)MSG_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_TaskSuspend((OS_TCB*)&StartTaskTCB,&err);
OS_CRITICAL_EXIT();
}
static void LED_PA1 (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
while (DEF_TRUE)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
}
}
static void LED_PB15 (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
while (DEF_TRUE)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET);
OSTimeDlyHMSM(0, 0, 2, 500,OS_OPT_TIME_HMSM_STRICT,&err);
}
}
static void send_msg (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
while (DEF_TRUE)
{
printf("hello uc/OS! 欢迎来到RTOS多任务环境!\r\n");
OSTimeDlyHMSM(0, 0, 2, 0,OS_OPT_TIME_HMSM_STRICT,&err);
}
}
static void AppTaskCreate (void)
{
}
static void AppObjCreate (void)
{
}
void Error_Handler(void)
{
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
6.硬件操作
将USB-TTL 的GND 和3.3V 接入STM32系统板 的GND 和3.3V ,然后TXD 和RXD 分别接入A10 和A9 引脚。
接入后按照程序中GPIOx 的引脚接上LED灯 ,且最小核心板要利用跳线帽实现boot0置1 ,boot1置0 。
打开 mcuisp 软件,开始烧录。
7.实际效果
(二)信号分析
1. Keil虚拟仿真逻辑仪
(1)设置参数
出现错误:error 65: access violation at 0x40023800 : no 'read' permission 根本原因:map地址空间权限映射有问题。部分地址空间没有读写的权限,程序不能自动运行。 解决方法: ① 在工程文件中新建debug,ini 文件,添加map地址权限映射代码
map 0x40000000, 0x40007FFF read write
map 0x40010000, 0x400157FF read write
map 0x40020000, 0x4007FFFF read write
map 0x50000000, 0x50060BFF read write
map 0x60000000, 0x60000FFF read write
map 0xE0000000, 0xE00FFFFF read write
② 在Debug 页添加ini 文件
(2)调试波形
打开逻辑分析仪和Serial Windows 中的UART #1 窗口,添加端口并将Display Type 设置为Bit 。
观察波形可知: PA1 引脚灯周期为1秒,PB15 引脚灯周期为3秒,USART1 每2秒输出一次
2. SaleaeLogic16真实逻辑仪
(1)下载软件
云盘下载:https://pan.baidu.com/s/1ycY6O-N0oK-Wj74uQRn0fg 提取码:0526 安装软件时驱动程序注册到系统,当插入SALEAE 16 逻辑分析仪后自动安装驱动。
(2)介绍软件
软件界面基本是左中右的布局,左边主要是采集和显示设置,右边是分析和解析设置,中间是波形显示区域。
无逻辑分析仪的时候显示 Disconnected ,左边是8个通道,使用的时候根据硬件连接选择对应通过进行配置,中间部分是显示逻辑波形的区域,右边是添加一些协议数据格式,数据分析区域。 点击 Start Simulation ,可在波形区域模拟显示出一些软件生成的数据,如果设置了解析,可以根据所设置的协议,生成一些符合协议解析要求的模拟数值。
解析设置方法: ① 默认的演示模式是 8 通道的,可设置成 16 通道的 ② 点击左上角的符号,弹出选择演示的设备 ③ 选择 LOGIC16 ,界面出现 SALEAE16 的设置
④ 通道设置:波形幅值比例,进行修改不同的比例
⑤ 触发方式选择:上升沿、下降沿、双边沿,主要跟协议信号有关系
⑥ Annotations 选项:增加测量电平时间的组数,类似示波器测量时间的标线
⑦ Analyzers 选项:包含很多协议,常用的串口、SPI、IIC、CAN 等都可进行测量
(3)观测波形
① 线路连接
逻辑分析仪 | STM32F103C8T6 |
---|
CH0 | PA1 | CH1 | PB15 | CH2 | PA9 | GND | GND |
连接逻辑分析仪后,左上角的采集按钮由以前的 START STMULTAR变成START。
② 波形采集
③ 波形分析
PA1 引脚输出电平周期为 1s,PB15 引脚输出电平周期为 3s,USART1 串口输出周期为 2s,与程序设计的周期相同。
④ 协议分析
使用UART通信协议进行一个字节数据的传输就是在信号线上产生八个高低电平的组合。数据传送速率用波特率来表示,即每秒钟传送的二进制位数。以9600,8-N-1(8个数据位,没有校验位,1位停止位)为例,串口波特率为9600,1bit的传输时间大约为1/9600=104us ,传送一个数据实际是传送10个比特(开始位-8个数据位-停止位),一个bytes的传输速率实际是9600*8/10=7680bps 。
UART 使用异步串行通信。异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,但在同一个字符中的两个相邻位间的时间间隔是固定的。
串口通信协议数据传送时,每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位): 起始位—先发出一个逻辑“ 0 ”信号,表示传输字符的开始 数据位—可以是 5~8 位逻辑“ 0 ”或“ 1 ” 校验位—数据位加上这一位后,使得“ 1 ”的位数应为偶数(偶校验)或奇数(奇校验) 停止位—它是一个字符数据的结束标志。可以是 1 位、1.5 位、2 位的高电平 空闲位—处于逻辑“ 1 ”状态,表示当前线路上没有资料传送
三、实验总结
在本次实验过程中,我学会了如何将uc/OS-III移植到stm32F103上并构建相关任务,学会了如何使用Keil虚拟仿真逻辑仪和SaleaeLogic16真实逻辑仪抓取LED输出电平和串口通信的波形,进行协议分析。其实,移植uc/OS-III到stm32F103上的过程并不难,但在添加工程组件和修改文件内容时需特别小心仔细,否则移植会不成功。在使用Keil虚拟仿真逻辑仪进行仿真调试的过程中,调试界面的左下角出现了error 65: access violation at 0x40023800 : no 'read' permission 的错误,最后通过查找此错误的相关资料,引入debug.ini 文件,才成功调试出波形。在使用SaleaeLogic16真实逻辑仪抓取LED输出电平和串口通信的波形时,出现了framing error ,通过查找资料我发现可能是因为波特率设置的问题,在最开始抓取波形时我并没有修改它的默认波特率9600,应该将波特率设置为115200的。
四、参考资料
1、uart通信协议详解 2、keil仿真和使用示波器调波形 3、STM32F103C8移植uCOSIII(HAL库) 4、STM32F103C8T6移植uCOS基于HAL库 5、STM32F103C8T6移植uC/OS-III基于HAL库超完整详细过程
|