STM32嵌入式实验报告
报告写作规范
1. 实验功能描述:按键控制LED,串口计算器
2. 电路硬件原理、接线等分析
3. 程序下载工具,超级终端等使用说明
4. 程序分析说明(基于OS的分析各任务结构、关系)
5. 实验中遇到的问题及解决办法分析
6. 总结,心得体会
一实验功能描述:
1. 一个按键调节1个LED的闪烁频率;(1-4秒)
2. 一个按键调节另1个LED的亮度变化周期;(1-4秒)
3. 整合串口计算器(要求超级终端内实现,带删除功能);
4. 以上内容采μc/os-Ⅱ用多任务操作系统方式实现。
二实验原理
本实验基于ALIENTEKMiniSTM32开发板进行,其板载资源非常丰富,本实验主要用到如下资源:
1.USB串口/串口1这是USB转串口(P4)同STM32F103RCT6的串口1进行连接的接口,标号RXD和TXD是USB转串口的2个数据口(对CH340G来说),而PA9(TXD)和PA10(RXD)则是STM32的串口1的两个数据口(复用功能下)。他们通过跳线帽对接,就可以和连接在一起了,从而实现STM32的程序下载以及串口通信。
2.两个LED灯这是开发板板载的两个LED灯,它们在开发板上的标号为:DS0和DS1。DS0是红色的,DS1是绿色的,主要是方便识别。一般的应用2个LED足够了,在调试代码的时候,使用LED来指示程序状态,是非常不错的一个辅助调试方法。ALIENTEK开发板几乎每个实例都使用了LED来指示程序的运行状态。
3.两个普通按键这是开发板板载的两个普通按键,可以用于人机交互的输入,这两个按键是直接连接在STM32的IO口上的,两个按键在开发板上的标号分别为:KEY0、KEY1。2.1实验分析
功能一:第一个按键调节1个LED的闪烁频率;(1-4秒)
本功能用到的硬件资源有:(1)指示灯DS0,DS0接PA8(2)定时器TIM3
(3)按键KEY_0
要实现按键改变LED的闪烁频率需要用STM32的定时器来产生PWM输出。使用TIM1的通道1产生PWM来控制DS0的亮度。通过按键改变闪烁频率,每按一次键修改一次TIM1_CCR1改变的梯度,从而改变输出PWM的占空比改变的频率,从而改变LED闪烁的频率。
配置步骤:
(1)开启TIM1时钟,配置PA8为复用输出。
(2)设置TIM3的ARR和PSC。
在开启了TIM1的时钟之后,我们要设置ARR和PSC两个寄存器的值来控制输出PWM的周期。当PWM周期太慢(低于50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM周期在这里不宜设置的太小。
(3)设置TIM1_CH1的PWM模式及通道方向。
设置TIM1_CH为PWM模式,因为DS0是低电平亮,而希望当CCR1的值小的时候,DS0就暗,CCR1值大的时候,DS0就亮,所以要通过配置TIM1_CCMR1的相关位来控制TIM1_CH1的模式。另外,我们要配置CH1为输出,所以要设置CC1S[1:0]为00。
(4)使能TIM1的CH1输出,使能TIM1。
接下来,需要开启TIM1的通道1的输出以及TIM1的时钟。前者通过TIM1_CCER来设置,是单个通道的开关,而后者则通过TIM1_CR1来设置,是整个TIM1的总开关。只有设置了这两个寄存器,这样我们才可能在TIM1的通道1上看到PWM波输出。
(5)设置MOE输出,使能PWM输出。
(6)修改TIM1_CCR1来控制占空比。
最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM1_CCR1则可以控制CH1的输出占空比。继而控制DS0的亮度。
功能二:一个按键调节另1个LED的亮度变化周期;(1-4秒)
本功能用到的硬件资源有:(1)指示灯DS1,DS1接PD2(2)按键KEY_1
通过调用Delay_ms()延时函数就可改变LED亮和暗的时间,初始时延时1秒,每按一次键,使延时增加1秒,当周期变成4秒的时候在回到1秒。
功能三:整合串口计算器(要求超级终端内实现,带删除功能)
本实验需要用到的硬件资源有: (1)串口1
本功能用到的串口1与USB串口并没有在PCB上连接在一起,需要通过跳线帽来连接一下。这里我们把P4的RXD和TXD用跳线帽与PA9和PA10连接起来。
串口基本配置直接相关的寄存器。1.串口时钟使能。串口作为STM32的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口1是在APB2ENR寄存器的第14位。2.串口复位。
3.串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR,通过设置该寄存器就可以达到配置不同波特率的目的。
4.串口控制。STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里只要用到USART_CR1。
5.数据发送与接收。STM32的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
6.串口状态。串口的状态可以通过状态寄存器USART_SR读取。
通过以上一些寄存器的操作外加一下IO口的配置,我们就可以达到串口最基本的配置了。
串口配置好后,就可以通过超级终端进行调试。要实现多个数的加减乘除,先要将输入的算式识别出来,再根据四则运算进行运算并回显。并且要实现回删功能,每检测到一个删除键就打印一个空格并打印一个将光标前移一位。
2.2 UCOSII配置步骤
实验要求用UCOSII实时操作系统实现上述三个功能。UCOSII的任何任务都是通过一个叫任务控制块(TCB)的东西来控制的,每个任务管理块有3个最重要的参数:
1.任务函数指针;
2.任务堆栈指针;
3.任务优先级;
任务控制块就是任务在系统里面的身份证。运行UCOSII的步骤如下:1)移植UCOSIIALIENTEK提供的SYSTEM文件夹里面的系统函数直接支持UCOSII,只需要在sys.h文件里面将:SYSTEM_SUPPORT_UCOS宏定义改为1,即可通过delay_init函数初始化UCOSII的系统时钟节拍,为UCOSII提供时钟节拍。2)编写任务函数并设置其堆栈大小和优先级等参数。编写任务函数,以便UCOSII调用。设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌套层数多,那么相应的堆栈就得大一些。
3)初始化UCOSII,并在UCOSII中创建任务调用OSInit,初始化UCOSII,通过调用OSTaskCreate函数创建我们的任务。4)启动UCOSII调用OSStart,启动UCOSII。通过以上4个步骤,UCOSII就开始在STM32上面运行了,这里还需要注意我们必须对os_cfg.h进行部分配置,以满足我们自己的需要。
三程序下载工具,超级终端等使用说明
3.1程序下载工具说明
STM32F103的程序下载有多种方法:USB、串口、JTAG、SWD等,这几种方式,都可以用来给STM32F103下载代码。本实验通过串口给STM32F103下载代码。STM32的串口下载一般是通过串口1下载的,本手册的实验平台ALIENTEKMiniSTM32F103,不是通过RS232串口下载的,而是通过自带的USB串口来下载。看起来像是USB下载(只需一根USB线,并不需要串口线)的,实际上,是通过USB转成串口,然后再下载的。
一键下载电路,则利用串口的DTR和RTS信号,分别控制STM32的复位和B0,配合上位机软件(flymcu,即mcuisp的最新版本),设置:DTR的低电平复位,RTS高电平进BootLoader,这样,B0和STM32的复位,完全可以由下载软件自动控制,只需要安装CH340G的驱动即可。
安装CH340G的驱动后,选择要下载的.hex文件,选择对应的串口再把DTR的低电平复位,RTS高电平进BootLoader,flymcu就会通过DTR和RTS信号来控制板载的一键下载功能电路。
3.2超级终端使用说明
由于windows10系统没有自带的超级终端,需要自己上网下载超级终端。其配置过程如下:
1.输入连接名称
2.选择对应的串口
3.把波特率设置为9600,数据控制流选为:无
4.进入属性进行ASCII码设置,将前两个方框勾上,点击确定即完成超级终端配置。
四程序分析说明(基于OS的分析各任务结构、关系)
由于要实现三个功能,所以除了开始任务之外,还要设置三个任务,LED0任务,LED1任务,计算器任务。主要程序如下:
#include
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "includes.h"
/////////////////////////UCOSII任务设置///////////////////////////////////
//START任务,设置任务优先级
#define START_TASK_PRIO 10 //开始任务优先级最低
//设置堆栈大小
#define START_STK_SIZE 64
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata);
//LED0任务
//优先级设置
#define LED0_TASK_PRIO 7
//设置堆栈大小
#define LED0_STK_SIZE 64
//任务堆栈
OS_STK LED0_TASK_STK[LED0_STK_SIZE];
//任务函数
void led0_task(void *pdata);
//LED1任务
//优先级设置
#define LED1_TASK_PRIO 6
//设置堆栈大小
#define LED1_STK_SIZE 64
//任务堆栈
OS_STK LED1_TASK_STK[LED1_STK_SIZE];
//任务函数
void led1_task(void *pdata);
//计算器任务
//优先级设置
#define COMPUTE_TASK_PRIO 5
//设置堆栈大小
#define COMPUTE_STK_SIZE 256
//任务堆栈
OS_STK COMPUTE_TASK_STK[LED1_STK_SIZE];
//任务函数
void compute_task(void *pdata);
//主函数
int main(void)
{
Stm32_Clock_Init(9); //系统始终设置
delay_init(72); //延时初始化
LED_Init(); //初始化LED与硬件接口
OSInit(); OSTaskCreate(start_task,(void*)0,(OS_STK*)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO ); //创建开始任务
OSStart();
}
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
OS_ENTER_CRITICAL();//进入临界区
OSTaskCreate(led0_task,(void*)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);
OSTaskCreate(led1_task,(void*)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);
OSTaskCreate(compute_task,(void*)0,(OS_STK*)&COMPUTE_TASK_STK[COMPUTE_STK_SIZE-1],COMPUTE_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO);
OS_EXIT_CRITICAL();//退出临界区
//LED0任务,按键实现LED0亮暗周期变化周期
void led0_task(void *pdata)
{
u8 t;
static u8 KEY_LED=1;
u16 led0pwmval=0;
u8 dir=1;
KEY_Init(); //按键初始化
uart_init(72,9600); //串口初始化
TIM1_PWM_Init(899,0);//PWM频率=72000/(899+1)=80Khz
while(1)
{
delay_ms(10);//去抖动
t=KEY_Scan(0);//获得键值
switch(t)
{
case KEY0_PRES:
KEY_LED++;
break;
default:
delay_ms(10);
}
if(KEY_LED%4==0)
{
delay_ms(10);
if(dir)
led0pwmval+=40;
else
led0pwmval-=40;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
LED0_PWM_VAL=led0pwmval;
}
if(KEY_LED%4==1)
{
delay_ms(10);
if(dir)
led0pwmval+=20;
else
led0pwmval-=20;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
LED0_PWM_VAL=led0pwmval;
}
if(KEY_LED%4==2)
{
delay_ms(10);
if(dir)
led0pwmval+=10;
else
led0pwmval-=10;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
LED0_PWM_VAL=led0pwmval;
}
if(KEY_LED%4==3)
{
delay_ms(10);
if(dir)
led0pwmval+=5;
else
led0pwmval-=5;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
LED0_PWM_VAL=led0pwmval;
}
};
}
//LED1任务,实现按键改变LED闪烁频率
void led1_task(void *pdata)
{
u8 num;
u8 a;
static u8 KEY_N=1;
LED1=1;
while(1)
{
for(num=0;num
{
a=KEY_Scan(1);//获得键值
switch(a)
{
case KEY1_PRES:
KEY_N++;
break;
default:
delay_ms(10);
}
if(KEY_N==5)
KEY_N=1;
delay_ms(1000);
}
LED1=~LED1;
};
}
void compute_task(void *pdata)
{
u8 i;
u8 j;
u8 t;
u8 s;
u8 index;
u8 num_of_delete=0;//删除符个数
u8 rest_delete=0; //剩余删除符数
u8 flag=0;
u8 num_of_operator=0; //运算符数
u8 num_of_product=0; //乘除运算符数
u8 rest_operator=0; //剩余运算符数
u8 rest_product=0; //剩余乘除运算符数
u8 len; //输入字符长度
u32 num[10]={0,0,0,0,0,0,0,0,0,0}; //存放要计算的数
int locate[10]={-1,0,0,0,0,0,0,0,0,0}; //存放运算符所在位数
u8 Operator[10]={0,0,0,0,0,0,0,0,0,0}; //存放运算符
u16 times=0;
u32 mid_result=0; //中间值
u32 final_result=0; //最后值
uart_init(72,9600);
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到数据长度
printf("输入的式子为\r\n\r\n");
for(t=0;t
{
if(USART_RX_BUF[t]==8)//与删除符输出空格
{
printf(" ");
printf("%d",8);
}
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//输入结束
}
//对删除键进行处理
for(t=0;t
{
if(USART_RX_BUF[t]==8)
num_of_delete++;
}
rest_delete=num_of_delete;
while(rest_delete!=0)//没有删除键时退出
{
for(t=0;t
{
if(USART_RX_BUF[t]==8)
{
flag=t;
break;
}
}
for(i=flag;i
{
USART_RX_BUF[i-1]=USART_RX_BUF[i+1];
}
rest_delete--;
len=len-2;
}
//获取运算符并存入数组
for(t=0;t
{
if(USART_RX_BUF[t]<48)
{
Operator[num_of_operator]=USART_RX_BUF[t]; locate[num_of_operator+1]=t;
num_of_operator++;
if(USART_RX_BUF[t]=='*'||USART_RX_BUF[t]=='/')
num_of_product++;
}
}
rest_operator=num_of_operator;
locate[num_of_operator+1]=len;
rest_product=num_of_product;
printf("\r\n\r\n");
//获取操作数并存入数组
for (s=0;s
{
for(i=locate[s]+1;i
{
num[s]=10*num[s]+(USART_RX_BUF[i]-48);
}
}
while(rest_product!=0)
{
//计算第一个乘除运算,并将两个数组分别前移,直到乘除运算符数为0
for(j=0;j
{
if(Operator[j]=='*'||Operator[j]=='/')
{
if(Operator[j]=='*')
mid_result=num[j]*num[j+1];
else
mid_result=num[j]/num[j+1];
rest_product--;
rest_operator--;
index=j;
num[index]=mid_result;
break;
}
}
for(j=index;j<8;j++)
{
num[index]=mid_result;
num[j+1]=num[j+2];
}
printf("%d",index);
for(j=index;j<8;j++)
{
Operator[j]=Operator[j+1];
}
}
//实现加减
if(rest_operator==0)
{
final_result=num[0];
}
//实现连续加减
else
{
final_result=num[0];
for(j=0;j
{
if(Operator[j]=='+')
final_result=final_result+num[j+1];
else
final_result=final_result-num[j+1];
}
rest_operator=0;
}
printf("\r\n\r\n答案为:\r\n\r\n");
for(t=0;t
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);
}
printf("=%d",final_result);/
//局部变量清零
for(i=0;i<10;i++)
{
num[i]=0;
}
for(i=0;i<10;i++)
{
Operator[i]=0;
}
num_of_operator=0;
rest_product=0;
num_of_product=0;
rest_operator=0;
num_of_delete=0;
rest_delete=0;
flag=0;
printf("\r\n\r\n");
USART_RX_STA=0;
}
else
{
times++;
if(times%400==0)printf("请输入算式以回车结束\r\n");
delay_ms(10);
}
}
}
五实验中遇到的问题及解决办法分析
1.超级终端无法输入数据
没有进行ASCII设置,将“以换行符作为末尾”和“本地回显输入的字符”两个选项勾上就可在超级终端中输入算式。
2.多个数的加减乘除运算输出结果错误
输出结果总是错误,于是将捕获的字符,识别的操作数数组,运算符数组等中间变量一一打印,观察每一步输出结果正确与否,一步一步调试,终于得以解决。
3.当在超级终端之中算式输入完成,按回车进行运算时,STM32总是死机
计算器任务中定义了很多局部变量,仔细查阅STM32手册后发现,在进行UCOSII任务配置时,将计算器任务堆栈设置的太小,CPU进入HardFault死机,将堆栈改大之后,不再出现死机的情况。
六总结,心得体会
通过这学期嵌入式课的学习,我对嵌入式操作系统有了深入的了解,尤其是UCOSII实时操作系统,它是一种占先式内核,对任务完成时间及其严格,能够方便的移植到各种嵌入式处理器,且占用资源少,可靠性高。
通过STM32的几个实验,增强了自己的动手能力。尤其是串口计算器实验,有一定的难度,要设计合理的算法,并将算法用代码实现,代码写完后还要进行不断的调试才能得到正确的结果。在做实验的过程中,我加深了对UCOSII操作系统的系统的理解,做到了理论与实际的结合。
在今后的学习和研究,我会更加注意将学到的理论知识应用到实际中去,真正做到理论与实际相结合。