蓝桥杯嵌入式备赛手册

本文是我参加蓝桥杯嵌入式比赛后的一些心得体会和一些本身总结的驱动代码,但愿能给之后参加蓝桥杯嵌入式的同窗带来一些帮助。web

本文没有通过校对,若有错误还请包涵,欢迎你们交流和指正,转载请注明出处。算法

1、 总述

首先说一下本身的状况:

我参加的是第九届蓝桥杯嵌入式比赛编程

省赛备赛两周(平均天天花费4——6小时),最后省一进入国赛小程序

国赛备赛一周半(平均天天花费4——6小时),最后国二数组

我是计算机科学与技术专业的学生,对模拟电路知识有所欠缺,因此客观题模拟电路部分只能看脸。服务器

以前有过使用stm32作小项目的经历能够快速上手(看到这里不要急,拿不拿奖与有没有stm32使用经历没有太大关系)框架

再谈谈对蓝桥杯嵌入式比赛的认识:

简介套话不说,这个比赛我我的认为得奖很容易,但想拿国一及以上就有些难度了,须要必定的训练加必定的运气。国二以上基本都是在客观题上分高低。异步

比赛考察范围有限且基本上都是套路,有限的知识和套路所有掌握了就能拿到国二,我也会在接下来的文章里把全部要掌握的点所有给你们梳理清楚svg

这个比赛主要针对的是电子工程类专业学生和计科学生,若是不是这些专业的学生就须要补充一些基础知识(C语言、微机原理、数电模电…)函数

比赛仍是主要面向双非学校学生,主要考察对stm32的使用(毕竟占总成绩70%的客观题就是写个小程序),其次在客观题会考察一些stm3二、Cortex-M三、嵌入式、数电模电的一些基础知识

我将会总结什么:

  • 如何构建工程
  • 我认为必须掌握的驱动及其运用
  • 怎么经过比赛提供的资料迅速完成咱们的程序
  • 我对考察点的我的心得,以及怎么分配咱们的备赛时间
  • 客观题的准备

PS:

这个比赛最大的优势也是最大的缺点即是:考察范围有限

看完这篇文章而且掌握我所提到的东西,至少能拿省一。欢迎交流与分享,切勿商用哦。

2、比赛提供

全部童鞋必定会问到比赛时提供什么,这一点官网也没有作很好的说明,在这里我说明一下:

2.1 省赛提供

2.1.1 省赛硬件方面

第八届和第九届的省赛都是只提供竞赛板,不涉及扩展板内容

2.1.2 省赛软件方面

省赛提供资料图:

这里写图片描述

  • CT117E(就是竞赛板)电路原理图和竞赛板使用说明

    比赛时若是忘记板上资源对应的引脚,电路原理图用来查看对应引脚。
  • 串口调试工具

    若是考察到串口(UART),须要用调试工具接收和发送来验证程序
  • 驱动及插件

    FT2232驱动和CooCox驱动,这些都用不到,不要管它
  • 数据手册

数据手册资料图:

这里写图片描述

这些数据手册各有各的用处,我认为最有用的是stm32f103rbt6.pdf,怎么看数据手册以及何时看后面会讲。

  • 液晶驱动参考例程

液晶驱动参考历程资料图:

这里写图片描述

一个已经写好的工程CT117E-LCD: 这个工程能够做为咱们进考场时测试板子及其屏幕是否正常工做,
    也能够直接在这个工程上构建咱们的程序,省略了咱们从新建工程的时间(我选择从新构建工程)

lcd驱动文件:一个.c源文件和两个.h头文件,这三个就是lcd的驱动文件,后面会讲到如何使用它们
  • i2c参考程序

    只有i2c.c和i2c.h两个文件,只在E2PROM和陀螺仪的时候能用到,后面会讲如何使用
  • STM32固件库v3.5

固件库资料图:

这里写图片描述

这个固件库十分关键,一方面要用来构建工程,一方面又能利用库函数和里面的Example快速编写驱动

2.2 国赛提供

在硬件上:竞赛板+扩展板

国赛在省赛提供的全部资料的基础上,增长了:

  • 扩展板的相关数据手册和电路原理图

  • DS18B20和DHT11的驱动

    只有.c和.h文件,没有工程和demo

3、构建工程

万里长城第一步,先把stm32工程给创建起来,构建工程须要用到比赛提供的STM32固件库和keil

keil的安装请参照网上的各类教程,多的数不清~

3.1 建议使用keil4

这里须要特殊说明一下,比赛时提供的是keil4,而若是你日常训练使用keil5的话主要有两点不一样:

  1. keil4没有代码自动联想补全功能,因此若是你使用keil5进行日常联系,我建议你关掉代码联想功能。
  2. keil4在设置那个Colink下程序进板与keil5有些许的差异,我下面在构建工程时会讲到如何设置。

综上所述,我建议在日常联系就跟比赛时同样采用keil4,避免一些没必要要的麻烦。

3.2 构建工程

首先在先在一个目录下(磁盘上的任何地方)创建一个文件夹

能够看到我在C盘目录下创建了一个名叫test的文件夹

这里写图片描述

以后在test文件夹下建五个文件夹

这里写图片描述

  • CORE: 用来存放内核文件
  • FWLIB:用来存放固件库的源文件和头文件
  • HARDWARE:用来存放驱动程序代码
  • OUTPUT: 用来存放在编译过程当中产生的中间文件
  • USER: 用来放用户文件和main函数文件,以及把工程项目创建在此处

接下来即是往这五个文件夹里copy东西了,打开比赛提供的STM32固件库V3.5

能够看到在文件夹“STM32F10x_StdPeriph_Lib_V3.5.0”的内容有:

这里写图片描述

给CORE文件夹里添加文件

  • 将Libraries->CMSIS->CM3->CoreSupport文件夹下的“core_cm3.c和core_cm3.h"文件放入CORE文件夹内
  • 将Libraries->CMSIS->CM3->DeviceSupport->ST->STM32F10x->startup->arm文件夹下的"startup_stm32f10x_md.s"放入文件夹内

最终CORE文件夹内的文件有:

这里写图片描述

给FWLIB文件夹里添加文件

  • 将Libraries->STM32F10x_StdPeriph_Driver文件夹下的“inc文件夹和src文件夹”一块儿放入FWLIB文件夹内

最终FWLIB文件夹内的文件有:

这里写图片描述

给USER文件夹里添加文件

  • 将Libraries->CMSIS->CM3->CoreSupport->ST->STM32F10x文件夹下的“stm32f10x.h、system_stm32f10x.c和system_stm32f10x.h”放入USER文件夹内
  • 将Project->STM32F10x_StdPeriph_Template文件夹下的“main.c、stm32f10x_conf.h、stm32f10x_it.c.c和stm32f10x_it.h”放入USER文件夹内

最终USER文件夹内的文件有:

这里写图片描述

打开keil4,新建工程

点击keil4中Project->New uVision Project:

这里写图片描述

将工程创建在咱们的test->USER文件夹下,命名为test:

这里写图片描述

芯片选择为STM32F103RB:

这里写图片描述

添加工程文件

点击这个组建来修改和添加目录和文件:

这里写图片描述

将Project Targets修改成"test",test下增长4个Groups分别为"CORE"、“FWLIB”、“USER”、“HARDWARE”:

这里写图片描述

点击Add Files,把CORE文件夹下的core_cm3.c和startup_stm32f10x_md.s添加到CORE:

这里写图片描述

把USER文件夹下的 main.c、stm32f10x_it.c和system_stm32f10x.c添加到USER:

这里写图片描述

FWLIB里面添加的是须要用到的库函数源文件,能够所有添加也能够只添加要用到的。我在这里把FWLIB->src文件夹下全部库函数源文件都添加进来(其实没有必要,但也仅仅是编译时慢了些):

这里写图片描述

此时,能够看到keil4里左侧的项目目录已经发生了变化

这里写图片描述

打开main.c把里面全部文件清空

写下如下代码:

int main()
{
	while(1)
	{
		
	}
}

写过嵌入式代码的童鞋应该对这两行代码并不陌生,main.c 文件最终样式:

这里写图片描述

对工程进行相关设置

点击Target Options…,进入工程设置的组件:

这里写图片描述

在Output中点击"Select Folder for Objects…":

这里写图片描述

选择OUTPUT文件夹:

这里写图片描述

在C/C++中的Define里填写

USE_STDPERIPH_DRIVER,STM32F10X_MD
  • 这一句话能够背过也能够在stm32f10x.h的95行找到:

这里写图片描述

填写后效果为:

这里写图片描述

随后点击Include Path的选项增长头文件路径,分别添加CORE、FWLIB->inc、HARDWARE、USER:

这里写图片描述

在Debug中点击右边的Use并选择CooCox Debugger:

这里写图片描述

点击Settings,并在Debug选项里选择Adapter为Colink:

这里写图片描述

再在Flash Download里点击Add,添加“STM32F10X Med-density Flash”:

这里写图片描述

最后一步也是keil4和keil5有区别的地方,keil4须要在Utilities中选择CooCox Debugger(这一步keil5不须要):

这里写图片描述

设置完后,编译程序并下板子

这下所有都设置完了,点击OK后,点击Build会获得 “0 Error(s), 0 Warning(s)”:

这里写图片描述

将生成的可执行文件下到板子里点击Download组件(若是下板子失败,把工程关闭从新打开,keil4有bug):

这里写图片描述

至此一个工程已经所有构建完毕

3.3 对于构建工程的我的建议

建议本身构建工程

你能够选择直接使用比赛时提供的液晶驱动例程,也能够选择本身构建工程。

我建议本身构建工程,我的认为液晶驱动例程的结构不清晰,本身构建的用着顺手。

驱动文件编写和存放

我把全部驱动文件都存在HARDWARE文件夹下。

每次新建一个文件,保存为.c或者.h文件都存在HARDWARE文件夹里,并在main.c中调用。

好比写LED的驱动,点击New就新建了一个文件,系统自动命名为"Text1":

这里写图片描述

接下来点击Save,将文件命名为"led.c"并保存到HARDWARE:

这里写图片描述

一样的再建立一个新文件,命名为"led.h"并保存到HARDWARE,双击左侧Project里的HARDWARE将"led.c"添加进工程:

这里写图片描述

添加完后能够看到左侧Project里的HARDWARE目录下出现了"led.c":

这里写图片描述

接下来即是编写"led.c"和"led.h"代码,以后在main.c里include "led.h"来使用。具体的驱动代码在后面会讲。

建议常常练习建工程

我建议每次练习一个题目都从新构建一遍工程,毕竟孰能生巧,在比赛时最好不要在准备工做上出问题。

4、快速编写必备驱动

4.1 LED

LED驱动其实就是单纯对GPIO进行操做。

可是这里加入了一个74HC573做为数据锁存器,因此每次操做须要给PD一个降低沿。

4.1.1 LED硬件链接

这里写图片描述

LED STM32
LED0 ~ 7 PC8 ~1 5
LE PD2

4.1.2 LED软件驱动

led.c:

#include "led.h"

void led_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = LED_ALL;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
}

void led_Control(u16 led, u8 state)
{
	if(state==1)
	{
		GPIO_ResetBits(GPIOC,led);
		GPIO_SetBits(GPIOD,GPIO_Pin_2);
		GPIO_ResetBits(GPIOD,GPIO_Pin_2);
	}
	else if(state==0)
	{
		GPIO_SetBits(GPIOC,led);
		GPIO_SetBits(GPIOD,GPIO_Pin_2);
		GPIO_ResetBits(GPIOD,GPIO_Pin_2);
	}
}

led.h:

#ifndef __LED_H
#define __LED_H

#include "stm32f10x.h"


#define LED_ALL GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15

#define LED1 GPIO_Pin_8
#define LED2 GPIO_Pin_9
#define LED3 GPIO_Pin_10
#define LED4 GPIO_Pin_11
#define LED5 GPIO_Pin_12
#define LED6 GPIO_Pin_13
#define LED7 GPIO_Pin_14
#define LED8 GPIO_Pin_15

void led_Init();
void led_Control(u16 led, u8 state);

#endif

4.1.3 LED驱动使用

在main.c文件中包含led.h头文件:

#include "led.h"

在main函数初始化阶段直接调用LED初始化函数初始化LED

int main()
{
    ...
    led_Init();
    ...
    while(1)
    {
        ...
    }
}

若是想点亮LED1则直接调用:

led_Control(LED1,1);

若是想让LED1~8全灭则调用:

led_Control(LED_ALL,0);

4.1.4 利用库函数的Example快速写LED驱动

LED的初始化驱动代码其实就是初始化了GPIO,设置对应引脚为推挽输出

LED的控制驱动代码就是对GPIO进行高低电平的输出

全是调用库函数来实现的,咱们能够没必要彻底背过每一个库函数的具体写法,直接从相应的库的Example里复制粘贴便可,大大减小比赛时间。

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->GPIO->IOToggle"文件夹下,打开"main.c"

能够看到里面有基本上全部LED初始化驱动所需的代码,直接复制,改改对应引脚和对应使能时钟便可:

这里写图片描述

  • 增长使能时钟GPIOC
  • 增长设置GPIO_Pin_8 ~ GPIO_Pin_15为推挽输出

设置引脚高低电平的函数能够在keil左侧的Project里找到"stm32f10x_gpio.h",打开后拉到文件最底找到:

这里写图片描述

若是快速找到这些代码,就能够大大节省敲代码所花费的时间,把更多的时间留在逻辑层,增长得奖的可能性

我我的的原则就是,能复制粘贴解决的事就不本身敲,由于这是比赛,有时间限制,要让本身得奖可能最大化

4.2 SysTick来实现延时函数

为何要先说延时函数呢?原本是要说按键驱动的,而按键我使用外部中断,利用延时函数来消抖,仍是先说一下延时函数。

4.2.1 毫秒延时函数

stm32f10x_it.c:

u32 delaytime;
void delay_ms(u32 time)
{
	delaytime=time;
	while(delaytime!=0);
}
...
void SysTick_Handler(void)
{
	delaytime--;
}

直接在stm32f10x_it.c文件中写上述代码,SysTick_Handler()的空函数在stm32f10x_it.c文件底部:

这里写图片描述

这里写图片描述

4.2.2 更改SysTick_Config

由于以后要用到咱们的延时函数来打断外部中断对按键进行消抖,因此要更改SysTick_Config的中断优先级。

然而SysTick_Config()函数在内核文件"core_cm3.h"中:

这里写图片描述

比赛规定内核文件是不容许被修改的,咱们直接把这个函数复制出来放到main.c中,从新命名为"SysTick_Config1()"

将这一行代码:

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);

修改成:

NVIC_SetPriority (SysTick_IRQn, 1);

复制粘贴和修改后main.c里的SysTick_Config1():

这里写图片描述

4.2.3 使用delay_ms()来延时

先在main()函数中进行相关初始化,这里连带着把中断分组也一块儿设置了:

int main()
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	SysTick_Config1(SystemCoreClock/1000);
	...
	while(1)
	{
	    ...
	}
}

若是想在某处延时500毫秒,直接调用:

delay_ms(500);

这里说一下,NVIC_PriorityGroupConfig()能够在"misc.h"里找到,SystemCoreClock能够在"system_stm32f10x.c"里找到

仍是那句话能复制粘贴就复制粘贴,省时间还减小出错率

4.3 KEY(基础板上的四个按键)

这四个按键有两个方法进行驱动

  • 循环扫描
  • 外部中断

我我的建议使用外部中断,由于咱们的嵌入式系统还要干其它工做,尤为若是要捕获PWM,刷新屏幕或者有时间间隔很小的中断任务要执行,循环扫描就不那么好使了。

但也不是绝对的,仍是要根据具体需求具体分析,可是使用外部中断来做为按键驱动目前来看还没发现什么缺点。

因此我强烈建议使用外部中断,而且我下面也会讲怎么用比赛提供的库函数Example简化咱们写驱动的任务。

4.3.1 KEY硬件链接

这里写图片描述

KEY STM32
KEY1 PA0
KEY2 PA8
KEY3 PB1
KEY4 PB2

4.3.2 KEY软件驱动

key.c:

#include "key.h"

void key_Init()
{
	EXTI_InitTypeDef   EXTI_InitStructure;
	GPIO_InitTypeDef   GPIO_InitStructure;
	NVIC_InitTypeDef   NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);
	EXTI_InitStructure.EXTI_Line = EXTI_Line8;
	EXTI_Init(&EXTI_InitStructure);
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_Init(&NVIC_InitStructure);
		
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
	EXTI_InitStructure.EXTI_Line = EXTI_Line1;
	EXTI_Init(&EXTI_InitStructure);
	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_Init(&NVIC_InitStructure);
		
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource2);
	EXTI_InitStructure.EXTI_Line = EXTI_Line2;
	EXTI_Init(&EXTI_InitStructure);
	NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
- [ ] 	NVIC_Init(&NVIC_InitStructure);
}

void EXTI0_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line0) != RESET)
  {

    EXTI_ClearITPendingBit(EXTI_Line0);
  }
}

void EXTI9_5_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line8) != RESET)
  {

    EXTI_ClearITPendingBit(EXTI_Line8);
  }
}

void EXTI1_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line1) != RESET)
  {
		
    EXTI_ClearITPendingBit(EXTI_Line1);
  }
}

void EXTI2_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line2) != RESET)
  {

    EXTI_ClearITPendingBit(EXTI_Line2);
  }
}

key.h:

#ifndef __KEY_H
#define __KEY_H


#include "stm32f10x.h"

key_Init();
#endif

4.3.3 KEY驱动的使用

在main()函数中调用key_Init()完成KEY驱动初始化:

int main()
{
    ...
    key_Init();
    ...
    while(1)
    {
        ...
    }
}

增长按键功能则只需在对应外部中断响应函数中添加代码便可,好比按下KEY1点亮LED1:

void EXTI0_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line0) != RESET)
  {
    
    led_Control(LED1,1);
    
    EXTI_ClearITPendingBit(EXTI_Line0);
  }
}

4.3.4 利用库函数的Example快速写KEY驱动

跟LED驱动同样,KEY驱动也能够在对应的库函数Example中找到大量咱们须要的代码,减小写驱动的时间。

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->EXTI->EXTI_Config"文件夹下,打开"main.c":

这里写图片描述

再打开"EXTI_Config"下的"stm32f10x_it.c":

这里写图片描述

有大量能够复制粘贴的代码,修改和增长咱们须要的部分便可。

4.4 LCD驱动的使用

直接把比赛提供的LCD驱动复制粘贴到咱们的HARDWARE,添加进工程

一共是三个文件:

  • lcd.c
  • lcd.h
  • fonts.H

4.4.1 初始化LCD

在main函数中调用:

int main()
{
    STM3210B_LCD_Init();
    delay_ms(500);
    LCD_Clear(White);
    LCD_SetTextColor(Blue);
    LCD_SetBackColor(White);
}
  • 记得在STM3210B_LCD_Init()函数后增长一个延时,我建议延时500ms,由于对外设的操做没有咱们MCU内部执行速度那么快。
  • LCD_Clear(White):使用白色来清屏
  • LCD_SetTextColor(Blue):设置文字颜色为蓝色,以后显示文字都是蓝色的
  • LCD_SetBackColor(White):设置背景颜色为白色,以后屏显示就是白底蓝字

这几个函数都能在lcd.h中找到,直接复制粘贴过来就行,不用死记硬背:

这里写图片描述

4.4.2 用LCD进行显示

4.4.2.1 写一个显示函数在循环中不停执行

我推荐的作法是写一个window()函数,在主函数main()的while(1)循环中每隔一段时间执行一次(即刷新屏幕)

好比我要完成这样一个任务:

  • 在第0行显示一个float类型的数据(保存两位小数),float类型数据叫作’test_float’
  • 在第2行显示"Hello World"
  • 每隔200ms刷新一次屏幕

我就这样写window()函数而且调用它:

void window()
{
    u8 str[20];
    
    LCD_ClearLine(Line0);
    sprintf(str,"test_float: %.2f",test_float);
    LCD_DisplayStringLine(Line0,str);
    
    LCD_ClearLine(Line2);
    sprintf(str,"Hello World");
    LCD_DisplayStringLine(Line2,str);
    
}

int main()
{
    ...
    STM3210B_LCD_Init();
    delay_ms(500);
    LCD_Clear(White);
    LCD_SetTextColor(Blue);
    LCD_SetBackColor(White);
    ...
    
    while(1)
    {
        window();
        delay(200);
    }
}

主要仍是要灵活运用

4.4.2.2 高亮显示

再好比,比赛中常常让你高亮显示某一行,高亮就是把蓝色的字设置为绿色或者其余亮一点的颜色的字

那么咱们再规定一个任务:

  • 在第0行显示一个float类型的数据(保存两位小数),float类型数据叫作’test_float’
  • 在第2行行显示一个int类型的数据,int类型数据叫作’test_int’
  • 经过按键KEY1选择是float高亮仍是int数据高亮,按一下切换一次
  • 每隔200ms刷新一次屏幕

咱们设置一个全局变量,默认是0:

u8 choose=0;

在KEY1按键中断函数中对全局变量choose进行改变:

void EXTI0_IRQHandler(void)
{
	delay_ms(5);
  if(EXTI_GetITStatus(EXTI_Line0) != RESET)
  {
    if(choose==0)
		{
			choose=1;
		}
		else
		{
			choose=0;
		}
    EXTI_ClearITPendingBit(EXTI_Line0);
  }
}

在window()函数里进行判断,若是是0就对float数据那一行高亮,若是是1就对int数据那一行高亮:

void window()
{
    u8 str[20];
    
    LCD_ClearLine(Line0);
    sprintf(str,"test_float: %.2f",test_float);
    if(choose==0)
    {
        LCD_SetTextColor(Green);
    }
    LCD_DisplayStringLine(Line0,str);
    LCD_SetTextColor(Blue);
    
    LCD_ClearLine(Line2);
    sprintf(str,"test_int: %d",test_int);
    if(choose==1)
    {
        LCD_SetTextColor(Green);
    }
    LCD_DisplayStringLine(Line2,str);
    LCD_SetTextColor(Blue);
    
}

这里请理解按键控制全局变量进行改变,显示函数根据变量来改变屏幕的输出

4.5 UART(串口)驱动

CT117E使用UART2链接外部串行接口,便可以直接使用UART2与上位机通讯。

4.5.1 UART硬件链接

这里写图片描述

总之就是初始化的时候初始化UART2,接受和发送的时候使用UART2便可

4.5.2 UART软件驱动

接收的时候使用中断接收,发送的时候主动发送并查询

usart.c:

void usart_Init()
{
    USART_InitTypeDef USART_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART2, &USART_InitStructure);

    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

    USART_Cmd(USART2, ENABLE);
}

void usart_SendString(u8 *str)
{
	u8 i=0;
	do{
		USART_SendData( USART2, str[i]);
		while(!USART_GetFlagStatus( USART2,  USART_FLAG_TXE));
		i++;
	}while(str[i]!=0);
}

void USART2_IRQHandler(void)
{
    u8 temp;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    
        temp= USART_ReceiveData(USART2);
    }
}

usart.h:

#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"

void usart_Init();
void usart_SendString(u8 *str);

#endif

4.5.3 UART驱动的使用

首先在main()函数里初始化UART:

int main()
{
    ...
    usart_Init();
    ...
    while(1)
    {
        ...
    }
}

若是每隔1s经过串口发送一个float类型数据的值(保留两位小数):

int main()
{
    u8 str[20];
    ...
    usart_Init();
    ...
    while(1)
    {
        sprintf(str,"test_float: %.2f",test_float);
        usart_SendString(str);
        delay_ms(1000);
    }
}

若是要接收字符串,给出一个任务:

  • 接受一串字符串以字符标志’S’结尾
  • 并在LCD上的第8行显示接收的字符串

能够这么作:

u8 RxBuffer[20];
u8 uart_index=0;
u8 receive=0;
void USART2_IRQHandler(void)
{
	u8 temp;
  if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
  {
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    
     temp= USART_ReceiveData(USART2);

    if((temp=='S')||(uart_index==20)) 
    {
			receive=1;
			uart_index=0;
      
      USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
    }
		else
		{
			RxBuffer[uart_index++]=temp;
		}
  }
}

void window()
{
    u8 str[20];
    if(receive==1)
	{
		LCD_ClearLine(Line8);
		sprintf(str,"%s",RxBuffer);
		LCD_DisplayStringLine(Line8, str);
		receive=0;
		for(i=0;i<20;i++)
			RxBuffer[i]=0;
		USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
	}
}

4.5.4 UART驱动的快速编写

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->USART->Interrupt"文件夹下,打开"main.c":

这里写图片描述

能够找到对应UART的初始化代码,还能够找到引脚初始化,中断初始化等等的代码:

这里写图片描述

把接收引脚改为PA3,发送引脚改为PA2便可,中断IRQChannel改为USART2_IRQn,再使能对应的时钟便可。

再打开同目录下的"stm32f10x_it.c"能够找到接收中断函数相关代码:

这里写图片描述

咱们使用查询的方式发送,因此不须要中断发送的相关代码。

4.6 E2PROM的驱动

对E2PROM的考察在近几年频繁出现,主要是E2PROM能够完成掉电存储的功能,这个东西几乎是必考,因此:

  • 由于比赛提供I2C驱动,因此了解下I2C协议便可,有兴趣也能够本身写一写I2C通讯驱动
  • 必须能熟练敲出对E2PROM的读写驱动(不只限于存储一个字节)

在我这篇博文里有讲如何使用IO口模拟I2C协议:

https://blog.csdn.net/zach_z/article/details/75331275

4.6.1 E2PROM的硬件链接

这里写图片描述

E2PROM STM32
SCL PB6
SDA PB7

4.6.2 E2PROM的软件驱动

直接把比赛提供的"i2c.c"和"i2c.h"复制粘贴到咱们工程目录里的HARDWARE文件夹里,并把i2c.c添加到工程里

在i2c.c中添加E2PROM的读写驱动代码:

void x24_write(u8 add,u8 data)
{
	I2CStart();
	
	I2CSendByte(0xa0);
	I2CWaitAck();
	
	I2CSendByte(add);
	I2CWaitAck();
	
	I2CSendByte(data);
	I2CWaitAck();
	
	I2CStop();
}

u8 x24_read(u8 add)
{
	u8 data;
	
	I2CStart();
	
	I2CSendByte(0xa0);
	I2CWaitAck();
	
	I2CSendByte(add);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);
	I2CWaitAck();
	
	data=I2CReceiveByte();
	I2CSendAck();
	
	return data;
}

4.6.2.1 读写后增长延时

这里须要注意,在每次执行完读或者写以后须要加一个延时函数(我延时2ms),由于MCU内部执行速度太快,而E2PROM外设跟不上内部时钟频率。

好比当执行如下代码:

u8 data=0;
data=x24_read(0x01);
data++;

MCU的执行速度太快,而外设反应跟不上,就会致使data可能尚未取得E2PROM里的数据的时候data就开始自增,致使程序预期结果和实际执行结果不同。

应该在读函数后增长一个2ms的延时函数:

u8 data=0;
data=x24_read(0x01);
delay_ms(2);
data++;

4.6.2.2 读写一个u32类型数据

比赛时不只仅会让读写一个Byte的数据,可能让读写int型或者float型数据,因此根据咱们读写一个Byte数据的驱动来灵活运用是很重要的。

读写一个u32类型的数据

void x24_write_int(u8 add, int data)
{
	x24_write(add,(u8)(data&0xff));
	delay_ms(2);
	x24_write(add+1,(u8)(data>>8&0xff));
	delay_ms(2);
	x24_write(add+2,(u8)(data>>16&0xff));
	delay_ms(2);
	x24_write(add+3,(u8)(data>>24&0xff));
	delay_ms(2);
}

int x24_read_int(u8 add)
{
	int data;
	data=(int)x24_read(add);
	delay_ms(2);
	data+=(int)x24_read(add+1)<<8;
	delay_ms(2);
	data+=(int)x24_read(add+2)<<16;
	delay_ms(2);
	data+=(int)x24_read(add+3)<<24;
	delay_ms(2);
	return data;
}

4.6.3 使用E2PROM驱动

首先要在main函数初始化时对i2c进行一个初始化,其次使用读函数读出咱们须要的数据,若是要写就直接写数据便可。

咱们规定这样一个任务:

  • 每次开机从E2PROM的0x01处读出一个u32类型数据(test_int)
  • 经过按键KEY1来存储这个数据的值到E2PROM的0x01处
#include "i2c.h"
u32 test_int=0;
int main()
{
    i2c_init();
    test_int=x24_read_int(0x01);
    while(1)
    {
        ...
    }
    
}

extern u32 test_int;
void EXTI9_5_IRQHandler(void)
{
	delay_ms(5);
    if(EXTI_GetITStatus(EXTI_Line8) != RESET)
    {
        x24_write_int(0x01,test_int);
        EXTI_ClearITPendingBit(EXTI_Line8);
    }
}

4.7 利用RTC完成计时

RTC在基础板也可能做为一个考察点,好比第九届省赛就让作个电子时钟,,其实也能够用其余计时方法,可是为了比赛准备更加充分,仍是要至少可以快速编写RTC驱动。

4.7.1 RTC计时软件驱动

规定这样一个任务:

  • 初始时间为 23-50-10
  • 到23-59-59后变为00-00-00
  • 在LCD屏的Line2上显示时间

这样完成任务:
建立"rtc.c"和"rtc.h"并保存到HARDWARE文件夹下,并在工程中添加"rtc.c"

rtc.c:

#include "rtc.h"
u32 Time=23*3600+50*60+10;
void rtc_Init()
{
	/* Enable PWR and BKP clocks */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  /* Allow access to BKP Domain */
  PWR_BackupAccessCmd(ENABLE);

  /* Reset Backup Domain */
  BKP_DeInit();

  /* Enable the LSI OSC */
  RCC_LSICmd(ENABLE);
  /* Wait till LSI is ready */
  while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
  {}
  /* Select the RTC Clock Source */
  RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

  /* Enable RTC Clock */
  RCC_RTCCLKCmd(ENABLE);

  /* Wait for RTC registers synchronization */
  RTC_WaitForSynchro();

  /* Wait until last write operation on RTC registers has finished */
  RTC_WaitForLastTask();

  /* Enable the RTC Second */
  RTC_ITConfig(RTC_IT_SEC, ENABLE);

  /* Wait until last write operation on RTC registers has finished */
  RTC_WaitForLastTask();

  /* Set RTC prescaler: set RTC period to 1sec */
  RTC_SetPrescaler(40000);

  /* Wait until last write operation on RTC registers has finished */
  RTC_WaitForLastTask();

  RTC_SetCounter(Time);
}

void RTC_IRQHandler(void)
{
  if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
  {
    if(RTC_GetCounter()==86400)
		{
			RTC_SetCounter(0x0);
		}

    /* Clear Interrupt pending bit */
    RTC_ClearITPendingBit(RTC_FLAG_SEC);
  }
}

led.h:

#ifndef __RTC_H
#define __RTC_H

#include "stm32f10x.h"

void rtc_Init();
extern u32 Time;

#endif

在main中调用RTC初始化函数,并在显示函数中编写相应时间显示代码:

#include "rtc.h"

void window()
{
    u8 str[20];
    Time = RTC_GetCounter();
    sprintf(str1,"%0.2d-%0.2d-%0.2d",Time/3600,(Time%3600)/60,(Time%3600)%60);
	LCD_DisplayStringLine(Line2, str);
}

int main()
{
    ...
    rtc_Init();
    while(1)
    {
        window();
        delay_ms(300);
    }
}

4.7.2 快速编写RTC驱动

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->RTC->LSI_Calib"文件夹下,打开"main.c":

这里写图片描述

直接复制粘贴RTC_Configuration()部分,去掉最后两句代码,加上咱们的计数值设定便可。

在相同目录下,打开"stm32f10x_it.c"还能够看到RTC中断函数相关代码,修改修改直接使用便可:

这里写图片描述

另外RTC_SetCounter()和RTC_GetCounter()也能够在库函数文件"stm32f10x_rtc.h"里找到,若是忘记能够在那里找:

这里写图片描述

4.8 定时器中断

定时器中断是很是有用的,不管是在省赛仍是在国赛,若是你想让你的嵌入式系统更加完美,运行效果更好,使用定时器中断必定会起到不少做用

4.8.1 TIM4定时器中断代码

tim.c:

#include "tim.h"
void tim4_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	
	TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
	
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
	
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);

    TIM_Cmd(TIM4, ENABLE);
}

void TIM4_IRQHandler(void)
{
	u8 str[20];
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
		
	}
}

tim.h:

#ifndef __TIM_H
#define __TIM_H

#include "stm32f10x.h"

void tim4_Init(u16 arr,u16 psc);

#endif

4.8.2 使用TIM4中断来完成任务

假设咱们设计:每隔50ms执行一次咱们全部完成的任务(好比使用UART发送字符串"hello\n",而且进行一次AD采样)

咱们使用TIM4来完成每隔50ms的定时器中断,使用TIM4的中断服务函数来执行咱们所要执行的任务。

在main函数中初始化TIM4为50ms执行一次,在TIM4的中断服务函数中执行串口发送和AD采样的任务:

#include "tim.h"
int main()
{
    ...
    tim4_Init(499,7199);
    while(1)
    {
        ...
    }
    
}

void TIM4_IRQHandler(void)
{
	u8 str[20];
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
    {
        usart_SendString(str);
        get_ADC();
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
		
	}
}

4.8.3 快速编写定时器中断驱动

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->TimeBase"文件夹下,打开"main.c":

这里写图片描述

这里写图片描述

  • 取用基础时钟设置的代码
  • 取用中断配置的代码
  • 中断设置改成TIM_IT_Update
  • 增长时钟使能

4.9 ADC

对ADC使用是考察的重点,第九届国赛和第八届国赛都考察到了,分值占比极大。

4.9.1 一路AD采样

若是没有用到扩展板,AD采样应该是会经过基础板上那个电位器R37来调节电压进行捕获考察ADC的使用

4.9.1.1 一路AD采样的硬件链接

这里写图片描述

这里写图片描述

能够根据数据手册看到,PB0对应着ADC的Channel_8

4.9.1.2 一路AD采样的软件驱动

adc.c:

void adc_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; 
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel = 1;
	ADC_Init(ADC1, &ADC_InitStructure);

	ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_13Cycles5);    

	ADC_Cmd(ADC1, ENABLE);   
	ADC_ResetCalibration(ADC1);

	while(ADC_GetResetCalibrationStatus(ADC1));
	ADC_StartCalibration(ADC1);

	while(ADC_GetCalibrationStatus(ADC1));
}

u16 get_adc()
{
	u16 value;
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	while(!ADC_GetFlagStatus(ADC1,  ADC_FLAG_EOC));
	value = ADC_GetConversionValue(ADC1);
	
	return value;
}

adc.h:

#ifndef __ADC_H
#define __ADC_H

#include "stm32f10x.h"

void adc_Init();
u16 get_adc();
#endif

4.9.1.3 一路AD采样的使用

好比每隔100ms对PB0上的电压进行AD采样并计算电压数值(保留2位小数),显示在LCD的Line1行上:

int main()
{
    u16 adc_value;
    u8 str[20];
    ...
    adc_Init();
    while(1)
    {
       adc_value=get_adc();
       sprintf(str," PB0: %.2f V",(float)adc_value/4096*3.3);
       LCD_DisplayStringLine(Line1, str);
       delay_ms(100);
    }
}

4.9.2 两路AD采样

两路AD采样确定是用到扩展板了,扩展板的

4.9.2.1 两路AD采样的硬件链接

这里写图片描述

这里写图片描述

扩展板使用跳线帽将PA4和AO1相连,PA5和AO2相连,因此通道4对经过电位器PR5调节的输出电压进行采样;通道5对经过电位器PR6调节的输出电压进行采样

4.9.2.2 两路AD采样的软件驱动

void adc_Init()
{
	ADC_InitTypeDef ADC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	
	 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
     ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 2;
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_55Cycles5);

    ADC_DMACmd(ADC1, ENABLE);
  
    ADC_Cmd(ADC1, ENABLE);

    ADC_ResetCalibration(ADC1);
    
    while(ADC_GetResetCalibrationStatus(ADC1));

     ADC_StartCalibration(ADC1);

    while(ADC_GetCalibrationStatus(ADC1));
}

u16 get_adc(u8 channel)
{
	u16 value;
	ADC_RegularChannelConfig( ADC1,channel,  1,  ADC_SampleTime_55Cycles5);
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	while(!ADC_GetFlagStatus( ADC1,  ADC_FLAG_EOC));
	value=ADC_GetConversionValue( ADC1);
	ADC_SoftwareStartConvCmd(ADC1, DISABLE);
	return value;
	
}

4.9.2.3 使用两路AD采样驱动

规定任务为:

  • 每隔100ms对两路AD进行采样
  • 获得两路AD的值,并计算对应电压
  • 将AO1的电压显示在LCD的Line3上
  • 将AO2的电压显示在LCD的Line4上
int main()
{
    u16 ao1,ao2;
    u8 str[20];
    ...
    adc_Init();
    while(1)
    {
        ao1=get_adc(ADC_Channel_4);
        ao2=get_adc(ADC_Channel_5);
        LCD_ClearLine(Line3);
        sprintf(str," AO1: %.2f V",(float)ao1/4096*3.3);
		LCD_DisplayStringLine(Line3,str);
		LCD_ClearLine(Line4);
		sprintf(str," AO1: %.2f V",(float)ao2/4096*3.3);
		LCD_DisplayStringLine(Line4,str);
		delay_ms(100);
    }
}

4.9.3 快速编写ADC驱动

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->ADC->ADC1_DMA"文件夹下,打开"main.c":

这里写图片描述

这里写图片描述

  • 更改咱们须要的对应引脚
  • 直接复制粘贴ADC初始化设置部分
  • 把两个ENABLE都改为DISABLE
  • 更改对应的转换通道个数和通道设置

获取adc值函数中用到的ADC库函数都能在"stm32f10x_adc.h"中找到,记不住就能够去复制粘贴

这里写图片描述

4.10 PWM

PWM是最最最重要的部分,咱们训练的目标在于

  • 输出两路占空比可调,频率可调的PWM
  • 捕获两路PWM的频率和占空比

这里必定要玩的很是清楚和熟练,PWM的工做原理我就不介绍了,自行学习

咱们要根据不一样的题目需求来选择不一样方式来捕获pwm或者输出pwm

4.10.1 PWM硬件链接

这里写图片描述

这里写图片描述

引脚 对应时钟通道 扩展版说明
PA1 TIM2_CH2 能够调节输入PWM的频率
PA2 TIM2_CH3 能够调节输入PWM的频率
PA6 TIM3_CH1 能够调节输入PWM的占空比
PA7 TIM3_CH2 能够调节输入PWM的占空比

4.10.2 PWM软件驱动

4.10.2.1 输出两路频率固定占空比可调的PWM

这种状况直接使用定时器的PWM输出模式便可

假设从PA1和PA2输出,咱们的驱动代码为:

void pwm_Init()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_1 | GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseStructure.TIM_Period = 999;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);

    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC3Init(TIM2, &TIM_OCInitStructure);
    TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM2, ENABLE);

    TIM_Cmd(TIM2, ENABLE);
}
  • 能够看到这里TIM2的预装载值为999,分频系数为71,证实咱们的频率为 72Mhz/(72*1000)=1Khz
  • 咱们直接设置了TIM2的CH2和CH3为PWM输出模式1,以及输出极性为高
  • 咱们经过TIM_SetCompare2()和TIM_SetCompare3()来改变两个通道输出PWM的占空比

好比咱们要求:

  • PA1输出占空比为10%
  • PA2输出占空比为20%
  • 按一下KEY1让PA1的占空比增长10%
u16 pa1=100,pa2=200;
int main()
{
    ...
    pwm_Init();
    TIM_SetCompare2(TIM2, pa1);
	TIM_SetCompare3(TIM2, pa2);
	while(1)
	{
	    ...
	}
}

void EXTI0_IRQHandler(void)
{
	delay_ms(5);
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        if(pa1<1000)
		    pa1+=100;
	    TIM_SetCompare2(TIM2, pa1);
		
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}
快速写PWM输出模式驱动:

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Output"文件夹下,打开"main.c":

这里写图片描述

找到能用的复制粘贴过来就行

4.10.2.2 输出两路频率可调占空比可调的PWM

这种状况就能够直接使用比较输出

规定任务:

  • 从PA6和PA7输出两路PWM
  • PA6频率为10KHz,占空比为10%
  • PA7频率为1KHz,占空比为20%
  • KEY1使PA6的占空比增长10%
  • KEY2使PA7的频率增长1KHz

则咱们这样写比较输出的驱动:
pwm.c

__IO uint16_t CCR1_Val =100; //72000000/72/10000
__IO uint16_t CCR2_Val =1000; //72000000/72/1000
float zhankong1=0.1;
float zhankong2=0.2;

void pwm_Init()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
	
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7 ;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
	TIM_TimeBaseStructure.TIM_Period = 65535;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

  
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);

    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);

 
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = CCR2_Val;

    TIM_OC2Init(TIM3, &TIM_OCInitStructure);

    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);
	
    TIM_Cmd(TIM3, ENABLE);

    TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2 , ENABLE);
}

uint16_t capture = 0;
u8 pa6_state=0,pa7_state=0;
void TIM3_IRQHandler(void)
{
  
  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
    capture = TIM_GetCapture1(TIM3);
		if(pa6_state==0)
		{
			TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *zhenkong1 );
			pa6_state=1;
		}
		else
		{
			TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *(1-zhankong1));
			pa6_state=0;
		}
  }

  
  if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
    capture = TIM_GetCapture2(TIM3);
		if(pa7_state==0)
		{
			TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val*zhankong2);
			pa7_state=1;
		}
		else
		{
			TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val*(1-zhankong2));
			pa7_state=0;
		}
  }
}

在主程序中调用以及按键的外部中断服务函数为:

int main()
{
    ...
    pwm_Init();
    while(1)
    {
        ...
    }
}
extern float zhankong1;
extern __IO uint16_t CCR2_Val;
u16 pinlv2=1000;
void EXTI0_IRQHandler(void)
{
	delay_ms(5);
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        zhankong1+=0.1
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

void EXTI9_5_IRQHandler(void)
{
	delay_ms(5);
    if(EXTI_GetITStatus(EXTI_Line8) != RESET)
    {
        pinlv2+=1000;
        CCR2_Val=(u16)(1000000/pinlv2);//72MHz/72
        EXTI_ClearITPendingBit(EXTI_Line8);
    }
}
  • 主要就是比较输出的驱动代码,剩下调整频率,调整占空比改变相应的全局变量便可
  • 改变频率就改变CCR1_Val和CCR2_Val的值
  • 改变占空比就改变zhankong1和zhankong2的值
快速写输出比较模式驱动:

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle"文件夹下,打开"main.c":

这里写图片描述

  • 基本上比较输出的设置代码都在这里,更改相应的引脚和通道就能够

再打开同目录下的"stm32f10x_it.h":

这里写图片描述

  • 这里中断服务函数已经有了输出相应频率的代码
  • 咱们对中断服务函数进行扩充增长占空比部分便可

4.10.2.3 捕获一路PWM的频率和占空比

若是只捕获一路PWM直接使用定时器的PWM输入模式便可

好比捕获PA6传来的PWM的频率和占空比:

void capture_Init()
{
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    	
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x0;

    TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
	
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

    TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);

    TIM_Cmd(TIM3, ENABLE);

    TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
}

__IO uint16_t IC2Value = 0;
__IO uint16_t DutyCycle = 0;
__IO uint32_t Frequency = 0;

void TIM3_IRQHandler(void)
{
  
  TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);

 
  IC2Value = TIM_GetCapture1(TIM3);

  if (IC2Value != 0)
  {
    
    DutyCycle = (TIM_GetCapture2 (TIM3) * 100) / IC2Value;

    
    Frequency = SystemCoreClock / IC2Value;
  }
  else
  {
    DutyCycle = 0;
    Frequency = 0;
  }
}
  • DutyCycle即为占空比
  • Frequency即为频率
  • 能够给时钟分频,可是在计算频率的时候就要除以分频数
快速写PWM输入模式驱动:

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle"文件夹下,"main.c"和"stm32f10x_it.c"分别有着PWM输入模式初始化和中断服务程序相关代码:

这里写图片描述

这里写图片描述

  • 只须要更改到你所需的引脚和对应的时钟通道便可

4.10.2.4 捕获两路PWM的频率和占空比

这时经过输入捕获来获得频率和占空比

原理图以下:

这里写图片描述

好比经过输入捕得到到PA6和PA7的频率和占空比:

void capture_Init()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	 NVIC_InitTypeDef NVIC_InitStructure;
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7|GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOA, &GPIO_InitStructure);;
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
	
	 TIM_TimeBaseStructure.TIM_Period = 65535;
  TIM_TimeBaseStructure.TIM_Prescaler = 71;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	
	
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  TIM_ICInitStructure.TIM_ICFilter = 0x0;

  TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
  TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	
  
  TIM_Cmd(TIM3, ENABLE);

  
  TIM_ITConfig(TIM3, TIM_IT_Update|TIM_IT_CC2|TIM_IT_CC1, ENABLE);

}


__IO uint16_t IC3ReadValue12 = 0, IC3ReadValue22 = 0;
__IO uint16_t CaptureNumber2 = 0;
__IO uint32_t Capture2 = 0;
__IO uint32_t TIM3Freq2 = 0;
u8 eage2=0;
u16 rising2=0,falling2=0;
float zhankong2=0;

__IO uint16_t IC3ReadValue11 = 0, IC3ReadValue21 = 0;
__IO uint16_t CaptureNumber1 = 0;
__IO uint32_t Capture1 = 0;
__IO uint32_t TIM3Freq1 = 0;
u8 eage1=0;
u16 rising1=0,falling1=0;
float zhankong1=0;

void TIM3_IRQHandler(void)
{ 
  if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET) 
  {
		if(eage2==0)
		{
			
			if(CaptureNumber2 == 0)
			{
				
				IC3ReadValue12 = TIM_GetCapture2(TIM3);
				CaptureNumber2 = 1;
			}
			else if(CaptureNumber2 == 1)
			{
				
				IC3ReadValue22 = TIM_GetCapture2(TIM3); 
				
				
				if (IC3ReadValue22 > IC3ReadValue12)
				{
					Capture2= (IC3ReadValue22 - IC3ReadValue12); 
				}
				else
				{
					Capture2 = ((0xFFFF - IC3ReadValue12) + IC3ReadValue22); 
				}
				
				TIM3Freq2 = (uint32_t) SystemCoreClock /72/ Capture2;
				CaptureNumber2 = 0;
			}
			
			rising2=TIM_GetCapture2(TIM3);
			TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling);
			eage2=1;
		}
		else if(eage2==1)
		{
			falling2=TIM_GetCapture2(TIM3);
			if(falling2>rising2)
			{
				zhankong2=(float)(falling2-rising2)/Capture2;
			}
			else if(falling2<rising2)
			{
				zhankong2=(float)(0xFFFF-falling2+rising2)/Capture2;
			}
			eage2=0;
			TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Rising);
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update|TIM_IT_CC2);
  }
	
	if(TIM_GetITStatus(TIM3, TIM_IT_CC1) == SET) 
  {
		if(eage1==0)
		{
			
			if(CaptureNumber1 == 0)
			{
				
				IC3ReadValue11 = TIM_GetCapture1(TIM3);
				CaptureNumber1 = 1;
			}
			else if(CaptureNumber1 == 1)
			{
				
				IC3ReadValue21 = TIM_GetCapture1(TIM3); 
				
				
				if (IC3ReadValue21 > IC3ReadValue11)
				{
					Capture1= (IC3ReadValue21 - IC3ReadValue11); 
				}
				else
				{
					Capture1 = ((0xFFFF - IC3ReadValue11) + IC3ReadValue21); 
				}
				
				TIM3Freq1 = (uint32_t) SystemCoreClock /72/ Capture1;
				CaptureNumber1 = 0;
			}
			
			rising1=TIM_GetCapture1(TIM3);
			TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling);
			eage1=1;
		}
		else if(eage1==1)
		{
			falling1=TIM_GetCapture1(TIM3);
			if(falling1>rising1)
			{
				zhankong1=(float)(falling1-rising1)/Capture1;
			}
			else if(falling1<rising1)
			{
				zhankong1=(float)(0xFFFF-falling1+rising1)/Capture1;
			}
			eage1=0;
			TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising);
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update|TIM_IT_CC1);
  }
}
  • 频率就是TIM3Freq1和TIM3Freq2
  • 占空比就是zhankong1和zhankong2
快速写输入捕获模式驱动:

在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->InputCapture"文件夹下,"main.c"和"stm32f10x_it.c"分别有着输入捕获模式初始化和中断服务程序相关代码:
这里写图片描述

这里写图片描述

  • 对比我在上面放的驱动和输入捕获官方例程,能用的就复制粘贴
这里再次强调,要把PWM的输入输出彻底掌握而且驱动越熟越好
第八届国赛中考察了两路频率可变的捕获和频率可变的输出
两路占空比的捕获和占空比可变的PWM捕获和输出可变的PWM将颇有可能在以后的比赛中碰到

4.11 扩展板的按键矩阵

扩展板的8个按键根据AD采样到相应引脚的电压来判断是哪一按键

前面ADC驱动已经讲过怎么写了,就跳过ADC的部分

扩展版按键经过跳线帽将PA5与AKEY相连

这8个按键须要注意的地方:

  • 若是按照板子上的电阻确实是跟原理图上阻值如出一辙,那么经过计算就能够获得每一个按键按下的电压值
  • 若是板子上电阻阻值跟原理图上有误差,那么直接开始试,从小到大开始一个一个按键试
  • 8个按键试出来也花不了10分钟,这是我得出的解决办法

直接贴出扩展版按键驱动:

#include "button.h"

void button_Init()
{
    ADC_InitTypeDef ADC_InitStructure;
	
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);

  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
      
    ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_55Cycles5);
    
    ADC_Cmd(ADC1, ENABLE);  
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

u8 get_button()
{
	u16 btn_tmp;
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	while(!ADC_GetFlagStatus( ADC1,  ADC_FLAG_EOC));
	btn_tmp=ADC_GetConversionValue(ADC1);
	
	if(btn_tmp <= 0x0020)
	{
		return 1;
	}
	else if((btn_tmp >= 0x00B0) && (btn_tmp <= 0x0100))
	{
		return 2;
	}
	else if((btn_tmp >= 0x0240) && (btn_tmp <= 0x0300))
	{
		return 3;
	}
	else if((btn_tmp >= 0x03B0) && (btn_tmp <= 0x0450))
	{
		return 4;
	}
	else if((btn_tmp >= 0x0450) && (btn_tmp <= 0x0700))
	{
		return 5;
	}
	else if((btn_tmp >= 0x0700) && (btn_tmp <= 0x0800))
	{
		return 6;
	}
	else if((btn_tmp >= 0x0840) && (btn_tmp <= 0x0940))
	{
		return 7;
	}
	else if(btn_tmp <= 0x0B50)
	{
		return 8;
	}
	else
	{
		return 0;	//error status & no key
	}
}

在主程序中循环扫描的办法来判断是否有按键按下

int main()
{
    u8 button;
    ...
    button_Init();
    while(1)
    {
        button=get_button();
        ...
    }
}

4.12 数码管

数码管在官方给的声明通知中说只考察静态,无论考不考,咱们准备好,知道怎么写驱动总归是不亏的。

4.12.1 数码管的硬件链接

这里写图片描述

PA1~3用跳线帽跟P3上的对应引脚相连便可

4.12.2 数码管的软件驱动

seg.c

#include "seg.h"

u8 seg[17]={ 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00}; 
void seg_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOA,GPIO_Pin_1);
	GPIO_ResetBits(GPIOA,GPIO_Pin_2);
	GPIO_ResetBits(GPIOA,GPIO_Pin_3);
}

void seg_Control(u8 bit1, u8 bit2, u8 bit3)
{
	u8 temp,i;
	
	temp=seg[bit3];
	for(i=0;i<8;i++)
	{
		if(temp&0x80)SER_H;
		else SER_L;
		
		temp=temp<<1;
		SCK_L;
		delay_ms(1);
		SCK_H;
	}
	
	temp=seg[bit2];
	for(i=0;i<8;i++)
	{
		if(temp&0x80)SER_H;
		else SER_L;
		
		temp=temp<<1;
		SCK_L;
		delay_ms(1);
		SCK_H;
	}
	
	temp=seg[bit1];
	for(i=0;i<8;i++)
	{
		if(temp&0x80)SER_H;
		else SER_L;
		
		temp=temp<<1;
		SCK_L;
		delay_ms(1);
		SCK_H;
	}
	
	RCK_L;
	delay_ms(1);
	RCK_H;
}

seg.h:

#ifndef __SEG_H
#define __SEG_H

#include "stm32f10x.h"


#define SER_H GPIO_SetBits(GPIOA,GPIO_Pin_1)
#define SER_L GPIO_ResetBits(GPIOA,GPIO_Pin_1)

#define RCK_H GPIO_SetBits(GPIOA,GPIO_Pin_2)
#define RCK_L GPIO_ResetBits(GPIOA,GPIO_Pin_2)

#define SCK_H GPIO_SetBits(GPIOA,GPIO_Pin_3)
#define SCK_L GPIO_ResetBits(GPIOA,GPIO_Pin_3)

void seg_Init();
void seg_Control(u8 bit1, u8 bit2, u8 bit3);

#endif

4.13 ds18b20驱动的使用

ds18b20是一个温度传感器

由于比赛提供ds18b20的驱动文件,因此直接把.c和.h文件添加到咱们的HARDWARE文件夹下,而且添加进工程便可。

这里写图片描述

PA6经过跳线帽与P3上对应引脚相连就能够驱动ds18b20温度传感器了

使用ds18b20获取温度后显示在LCD的第一行:

#include "ds18b20.h"
int main()
{
    u32 z;
    u8 str[20];
    ...
    ds18b20_init_x();
    while(1)
    {
        z=(ds18b20_read()&0x7ff);
     	LCD_ClearLine(Line0);
     	sprintf(str," t= %2.2f",(float)z/16.);
     	LCD_DisplayStringLine(Line0, str);
    }
}

4.14 DHT11驱动的使用

DHT11是一个温湿度传感器

由于比赛提供dht11的驱动文件,因此直接把.c和.h文件添加到咱们的HARDWARE文件夹下,而且添加进工程便可。

这里写图片描述

PA7经过跳线帽与P3上对应引脚相连就能够驱动dht11温湿度传感器了

规定一个任务

  • 使用dht11驱动得到适度显示在LCD第一行
  • 得到温度显示在LCD第二行
#include "dht11.h"
int main()
{
    u32 z;
    u8 str[20];
    ...
    dht11_init();
    while(1)
    {
        z=dht11_read();
     	LCD_ClearLine(Line1);
     	sprintf(str1," shidu= %2d %%",z>>24);
     	LCD_DisplayStringLine(Line1, str1);
     	LCD_ClearLine(Line2);
     	sprintf(str1," wendu= %3d ",(z>>8)&0xff);
     	LCD_DisplayStringLine(Line2, str1);
     	delay_ms(2000);
    }
}
  • 记得这个温湿度读取一次要等待一段时间才能顺利读取到下一次的数据(通常2s)

4.15 三轴传感器

这个东西通常不会考察,就像蜂鸣器同样,考察蜂鸣器考场会乱成一锅粥,这个东西也要拿着板子转来转去,并且它占用引脚也多,可是保不齐这几年把扩展板玩的没什么可考察的了,就出个三轴传感器的题目,最好仍是要掌握一下驱动怎么写。

这里写图片描述

  • PA4~7都不能做为其余用处,三周传感器须要使用到这四个引脚资源
  • P2所有短接

这个外设的通讯协议也是I2C跟咱们以前说到的E2PROM同样,因此咱们就轻车熟路了。

主要是如下两点:

  • 更改I2C驱动里的SDA和SCL引脚
  • 正确配置三轴传感器

修改I2C的引脚定义:
i2c.c:

/** I2C 总线接口 */
#define I2C_PORT GPIOA      
#define SDA_Pin	GPIO_Pin_5  
#define SCL_Pin GPIO_Pin_4

驱动代码为:

u8 alz[3] ;

//
void LIS302DL_Write(unsigned char reg,unsigned char info)
{	
	I2CStart(); 
	I2CSendByte(0x38);  
	I2CWaitAck();     
	I2CSendByte(reg);  
	I2CWaitAck(); 
	I2CSendByte(info); 
	I2CWaitAck();
	I2CStop();
}

//
uint8_t LIS302DL_Read(uint8_t address)
{
	unsigned char val;
	I2CStart(); 
	I2CSendByte(0x38);  
	I2CWaitAck();     
	
	I2CSendByte(address);  
	I2CWaitAck(); 
	
	I2CStart();
	I2CSendByte(0x39); 
	I2CWaitAck();
	val = I2CReceiveByte(); 
	I2CSendNotAck();
	I2CStop();
	
	return(val);
}

//
u8* Lis302DL_Output(void)
{
	if((LIS302DL_Read(0x27) & 0x08) != 0)
	{
		
		alz[0] = (LIS302DL_Read(0x29));  //x
		alz[1] = (LIS302DL_Read(0x2B));  //y
		alz[2] = (LIS302DL_Read(0x2D));  //z
	}

	return alz;
}

//
void LIS302DL_Config(void)
{
	LIS302DL_Write(CTRL_REG1,0x47);
	LIS302DL_Write(CTRL_REG2,0x00);
	LIS302DL_Write(CTRL_REG3,0xC1);
	LIS302DL_Write(FF_WU_THS_1,0x28);		
	LIS302DL_Write(FF_WU_DURATION_1,40);		
//	LIS302DL_Write(FF_WU_CFG_1,0x10);		
}

//
uint8_t LIS302DL_Check(void)
{
	if(LIS302DL_Read(0x0f))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

在main()函数中使用,并在LCD上打印出三个轴的角度:

int main()
{
    u8 *p;
    u8 str1[20];
    ...
    i2c_init();
	LIS302DL_Config();
	if(LIS302DL_Check() == 1)
 	{
 		LCD_DisplayStringLine(Line1, (u8 *)" MEMS STATUS: OK");
 	}
 	else
 	{
 		LCD_DisplayStringLine(Line1, (u8 *)" MEMS STATUS: ERROR");
 	    
 	}
 	while(1)
 	{
 	  p=Lis302DL_Output();
	
	  LCD_ClearLine(Line2);
 	  sprintf(str1," X= %d ",(int)p[0]);
 	  LCD_DisplayStringLine(Line2, str1);
	  LCD_ClearLine(Line3);
 	  sprintf(str1," Y= %d ",(int)p[1]);
 	  LCD_DisplayStringLine(Line3, str1);
	  LCD_ClearLine(Line4);
 	  sprintf(str1," Z= %d ",(int)p[2]);
 	  LCD_DisplayStringLine(Line4, str1);  
 	}
}

4.16 光敏电阻

这个也没什么讲的,要考察也不大可能考察到这里,看一眼驱动就好了

一共有两个光敏电阻,一个直接读引脚高低判断光强,一个是根据输入电压AD采样断光强

这里写图片描述

PA3直接读取引脚电平判断光强弱:

void DO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

//
int main(void)
{	
	SysTick_Config(SystemCoreClock/1000);
	
	STM3210B_LCD_Init();
	LCD_Clear(White);

	LCD_SetTextColor(White);
	LCD_SetBackColor(Blue);

	LCD_ClearLine(Line0);
	LCD_ClearLine(Line1);
	LCD_ClearLine(Line2);
	LCD_ClearLine(Line3);
	LCD_ClearLine(Line4);

	LCD_DisplayStringLine(Line1,(u8*)"    R-Photo DEMO    ");

	LCD_SetTextColor(Blue);
	LCD_SetBackColor(White);
	
	DO_Config();

	while(1)
	{

		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == Bit_RESET)
		{
			LCD_DisplayStringLine(Line7, (u8*)"       DO:High     ");
		}
		else
		{
			LCD_DisplayStringLine(Line7, (u8*)"       DO:Low      ");
		}
	}
}

PA4根据AD采样的值来判断光强大小,ADC以前有讲这里就不说了:

int main(void)
{	
	
	u8 str[20];
	u16 tmp = 0;
	
	SysTick_Config(SystemCoreClock/1000);
	
	STM3210B_LCD_Init();
	LCD_Clear(White);

	LCD_SetTextColor(White);
	LCD_SetBackColor(Blue);

	LCD_ClearLine(Line0);
	LCD_ClearLine(Line1);
	LCD_ClearLine(Line2);
	LCD_ClearLine(Line3);
	LCD_ClearLine(Line4);

	LCD_DisplayStringLine(Line1,(u8*)"    R-Photo DEMO    ");

	LCD_SetTextColor(Blue);
	LCD_SetBackColor(White);
	
	ADC_Config();

	while(1)
	{
		tmp = Read_ADC();
		snprintf((char *)str, sizeof(str), " R-P:%.2fK  ", tmp/(4096.-tmp)*10);
		LCD_DisplayStringLine(Line6, str);
		Delay_Ms(200);
	}
}

5、客观题

5.1 片上资源

片内资源是能够经过数据手册查找到的,具体能够打开考场提供数据手册"stm32f103rbt6.pdf"查看:

这里写图片描述

  • 128K闪存(flash)
  • 20K SRAM
  • 3个通用定时器(TIM二、TIM三、TIM4)
  • 一个高级定时器(TIM1)
  • 2个SPI (SP一、SPI2)
  • 2个I2C (I2C一、I2C2)
  • 3个USART (USART一、USART二、USART3)
  • 1个USB 2.0
  • 1个CAN 2.0B
  • 49个GPIO端口
  • 2个(12位ADC模块),都是16个通道
  • CPU频率是72MHz
  • 工做电压:2.0~3.6V
  • 工做温度: -40 ~ +85 / -40 ~ +105
  • 封装形式:LQFP64

5.2 可能会考到的stm32知识

可能考到的基本都在数据手册能找到,比赛时应该是提供英文版手册,我建议在比赛前把手册都看一遍,至少能知道对应知识点在哪里找。

5.2.1 嵌套的向量式中断控制器(NVIC)

这里写图片描述

STM32F103xx加强型产品内置嵌套的向量式中断控制器,可以处理多达43个可屏蔽中断通道(不包括 16个Cortex™-M3的中断线)和16个优先级。

  • 紧耦合的NVIC可以达到低延迟的中断响应处理
  • 中断向量入口地址直接进入内核
  • 紧耦合的NVIC接口
  • 容许中断的早期处理
  • 处理晚到的较高优先级中断
  • 支持中断尾部连接功能
  • 自动保存处理器状态
  • 中断返回时自动恢复,无需额外指令开销

记住16个优先级,5个优先级分组

5.2.2 外部中断/事件控制器(EXTI)

外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每一个中断线均可以独立地配置 它的触发事件(上升沿或降低沿或双边沿),并可以单独地被屏蔽;有一个挂起寄存器维持全部中断请 求的状态。EXTI能够检测到脉冲宽度小于内部APB2的时钟周期。多达80个通用I/O口链接到16个外 部中断线。

5.2.3 时钟和启动

统时钟的选择是在启动时进行,复位时内部8MHz的RC振荡器被选为默认的CPU时钟,随后能够 选择外部的、具失效监控的4~16MHz时钟;

当检测到外部时钟失效时,它将被隔离,系统将自动地 切换到内部的RC振荡器,若是使能了中断,软件能够接收到相应的中断。

一样,在须要时能够采起 对PLL时钟彻底的中断管理(如当一个间接使用的外部振荡器失效时)。

多个预分频器用于配置AHB的频率、高速APB(APB2)和低速APB(APB1)区域。

AHB和高速APB的最 高频率是72MHz,低速APB的最高频率为36MHz。

参考如图的时钟驱动框图:

这里写图片描述

最重要的是要经过时钟框图找到时钟源和相应设备的关系:

好比40KHz的RC振荡器HSI做为独立看门狗(IWDG)的时钟源:

这里写图片描述

5.2.4 DMA

DMA基本在客观题内算是必考,由于DMA是嵌入式里一个重要的传输手段,而主观题又很差考察,因此必定会在客观题上考察DMA的相应知识点

灵活的7路通用DMA能够管理存储器到存储器、设备到存储器和存储器到设备的数据传输;DMA控 制器支持环形缓冲区的管理,避免了控制器传输到达缓冲区结尾时所产生的中断。

每一个通道都有专门的硬件DMA请求逻辑,同时能够由软件触发每一个通道;传输的长度、传输的源地 址和目标地址均可以经过软件单独设置。

DMA能够用于主要的外设:SPI、I 2 C、USART,通用、基本和高级控制定时器TIMx和ADC。

STM32 的 DMA 有如下一些特性:

  1. 每一个通道都直接链接专用的硬件 DMA 请求,每一个通道都一样支持软件触发。这些功能 经过软件来配置。
  2. 在七个请求间的优先权能够经过软件编程设置 (共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。
  3. 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。
  4. 支持循环的缓冲器管理
  5. 每一个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。
  6. 存储器和存储器间的传输
  7. 外设和存储器,存储器和外设的传输
  8. 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设都可做为访问的源和目标。
  9. 可编程的数据传输数目:最大为 65536

5.2.5 看门狗

5.2.5.1 独立看门狗

独立的看门狗是基于一个12位的递减计数器和一个8位的预分频器,它由一个内部独立的40kHz的RC 振荡器提供时钟

由于这个RC振荡器独立于主时钟,因此它可运行于停机和待机模式。它能够被当 成看门狗用于在发生问题时复位整个系统,或做为一个自由定时器为应用程序提供超时管理。

经过 选项字节能够配置成是软件或硬件启动看门狗。在调试模式下,计数器能够被冻结。

5.2.5.2 窗口看门狗

窗口看门狗内有一个7位的递减计数器,并能够设置成自由运行。它能够被当成看门狗用于在发生问 题时复位整个系统。

它由主时钟驱动,具备早期预警中断功能;在调试模式下,计数器能够被冻结

5.3 其它方面的知识

客观题没办法速成,仍是在于日常的积累,咱们只能有针对性的去了解一些方面

  • 同步通讯和异步通讯的区别
  • 嵌入式系统的存储体系
  • 串行总线和并行总线的区别
  • 实时操做系统的知识
  • 数组电路的知识
  • 模拟电路的知识和计算(主要是二极管、三极管、运算器、放大器)

这些知识掌握的越多越好

6、比赛心得

6.1 牢记各个引脚的功能

能够外接的引脚一共只有8个(PA1-7),而且跟扩展板相连是驱动扩展板全部外设的引脚

相信在练习比赛模拟题的过程当中,脑子里就会逐渐对PA1-7愈来愈熟。

虽然说这些引脚对应的功能,对应的时钟通道咱们都能在相应手册上查到,可是比赛时寸秒寸金,但愿能在脑子里有下面一个表格:

GPIO 功能
PA1 TIM2_CH2 / SER(74LS595串行数据输入引脚) / 频率可调脉冲
PA2: TIM2_CH3 / USART2_TX / RCK(74LS595串行存储时钟输入引脚) / 频率可调脉冲
PA3 USART2_RX / SCK(74LS595串行移位时钟输入引脚) / 光敏电阻读入引脚
PA4 ADC1_IN4 / SCL(三轴传感器时钟引脚)
PA5 ADC1_IN5 / SDA(三轴传感器数据引脚) / 4*2按键矩阵
PA6 TIM3_CH1 / 占空比可调脉冲 / 温度传感器
PA7 TIM3_CH2 / 占空比可调脉冲 / 温湿度传感器

6.2 如何备赛

整体路线:

  • 首先先把每一个独立的驱动搞懂,可以作到顺利编写出来并运用
  • 其次再作前几年的模拟题、省赛题、国赛题上
  • 最后给本身出题,出本身认为可能考的部分,可是前几年没考的

6.2.1 快速编写驱动

若是你看了我上面对每一个驱动代码的编写,你就能知道其实大部分的代码咱们都不须要本身敲。

比赛提供的库函数的Example中就有着咱们基本须要驱动的代码,咱们只须要更改到咱们须要的(时钟、引脚等等)便可。

一些函数若是忘记怎么写,对应库函数的头文件也能够找到。

6.2.2 每作一次新的赛题从新开始

但愿每次从新作一道主观题时,都能从从新构建工程->从新编写每一个驱动代码->写逻辑层上的代码

由于比赛时和日常作东西不同,即使你认为本身原理都懂、驱动都知道怎么写,若是没有作到很是熟练,不免会出现小失误,而小失误有可能会耽误大量的时间

因此既然选择了参加比赛,那么咱们就指望准备约充分越好,对工程创建约熟练越好、基础驱动编写越快越好

6.2.3 对备赛时间的分配要有规划

若是你是小白,那么必定是越早开始备赛越好,一方面学习stm32的应用知识,另外一方面不断地练习完成一个嵌入式系统

若是你有必定的5一、stm32或是其余嵌入式设备的开发基础,那么根据你的学习能力来分配备赛时间

我省赛前一共用了两周时间大概是这么分配的:

  • 第一周完成工程创建和基础驱动的编写(天天建一次工程,完成一到两个小项)
  • 个人小项准备顺序(LED -> Systick(delay_ms()) -> KEY -> LCD -> ADC -> RTC -> USART -> E2PROM(I2C) )
  • 第二周开始练习往届题目,作了14~17年四年间的一些模拟题和省赛题

刚开始作模拟题确定会很慢,七、8个小时才能完成任务,训练两三届的题就会愈来愈顺手了

6.3 如何比赛

6.3.1 不要提早离场

比赛时候总有一些同窗提早离场,也不知道是所有作出信心满满仍是作不出来直接放弃

我认为既然已经选择了来比赛,比赛前也作了那么多的准备,就应该尽最大的努力去完成比赛

若是已经作出来了程序的全部任务,觉着比赛稳了:

  • 能够再检查检查本身的客观题,毕竟国二以上都是在客观题上分高低
  • 还能够检查检查本身的程序,多测试几组数据看看有无bug
  • 还能够修改修改代码,简化算法,添加注释什么的

总之,准备几个星期就为了这5个小时,提早走总有些得不偿失

6.3.2 对比赛时间的分配要有规划

省赛每一个地方不同,可能不会让提早进场,我就假设5个小时的时间:

  • 拿到题后先看一遍主观题,在内心留下个印象
  • 开始作客观题,客观题通常也不会耗费多少时间,20分钟顶天了,在作客观题的时候大脑也在想主观题的框架
  • 作完客观题记得提交一遍,开始作主观题写程序
  • 若是提早作完固然好,检查检查,优化优化,排查一切可能出现的bug
  • 若是临比赛结束还有半小时没写完,先把现有的程序作成能顺利运行不会宕机的程序,提交一遍
  • 必定要保证本身在还有半小时结束时客观题和程序都提交了一遍,先保证本身有个分

国赛通常提早半小时能够进赛场,排队进,早排队的大概能比最后的早进十几分钟,因此尽可能早去多争取十几分钟:

  • 提早进场后,不给发板子,可是咱们能够利用这20分钟先创建个工程,写个LED和按键的驱动(LED和按键确定要考)
  • 充分利用进场的时间,若是作的好,应该能够完成工程创建、LED、按键、LCD所有的工做
  • 拿到板子后,先上电测一下看看是否正常
  • 拿到赛题后,先看一遍主观题,直接作客观题,作完必定记得提交
  • 国赛的后半小时会乱糟糟,不少人提早离场,因此仍是在离结束半小时时调通程序,必定要提交一遍全部客观题和程序

再次说明一下离结束半小时提交的重要性:

  • 后半小时是提交高峰,赛点服务器拥堵,若是放到最后几分钟提交可能会有提交不成功现象发生
  • 后半小时不少人提早交卷离场,来来回回进进出出,万一哪一个麻瓜碰了一下你的电源线,直接GG
  • 后半小时理论上不会有什么太大的改变,可是你提交过一次后,最起码内心有底,不会很慌,说不定还会有突破,解决了没解决的问题

6.3.3 碰到问题千万不要放弃

全部比赛基本上都会碰到问题:ACM、电赛、咱们参加的蓝桥杯都同样,都会碰到或大或小的问题

要明确一点:

  • 若是一点难度都没有就拿奖证实你的水平已经高于这个比赛了,那么你拿奖是理所应当,并无值得高兴的
  • 碰到问题,遇到困难,想办法解决了,得到最后成功才是最快乐的

碰到问题必定要用本身掌握的知识和训练的经验去一步步解决,冷静分析,不要放弃

6.4 第八届嵌入式国赛代码例子

根据实例来理解我写的各个驱动的使用方法,代码能够到“山不高海不深”公众号中回复“第八届嵌入式国赛代码”
在这里插入图片描述

PS:回复“第五届嵌入式代码”还能够看看第五届的试题完成代码

最后祝愿各位参赛的同窗都能有个理想的成绩

——西安邮电大学 张凯捷