本文是我参加蓝桥杯嵌入式比赛后的一些心得体会和一些本身总结的驱动代码,但愿能给之后参加蓝桥杯嵌入式的同窗带来一些帮助。web
本文没有通过校对,若有错误还请包涵,欢迎你们交流和指正,转载请注明出处。算法
我参加的是第九届蓝桥杯嵌入式比赛编程
省赛备赛两周(平均天天花费4——6小时),最后省一进入国赛小程序
国赛备赛一周半(平均天天花费4——6小时),最后国二数组
我是计算机科学与技术专业的学生,对模拟电路知识有所欠缺,因此客观题模拟电路部分只能看脸。服务器
以前有过使用stm32作小项目的经历能够快速上手(看到这里不要急,拿不拿奖与有没有stm32使用经历没有太大关系)框架
简介套话不说,这个比赛我我的认为得奖很容易,但想拿国一及以上就有些难度了,须要必定的训练加必定的运气。国二以上基本都是在客观题上分高低。异步
比赛考察范围有限且基本上都是套路,有限的知识和套路所有掌握了就能拿到国二,我也会在接下来的文章里把全部要掌握的点所有给你们梳理清楚svg
这个比赛主要针对的是电子工程类专业学生和计科学生,若是不是这些专业的学生就须要补充一些基础知识(C语言、微机原理、数电模电…)函数
比赛仍是主要面向双非学校学生,主要考察对stm32的使用(毕竟占总成绩70%的客观题就是写个小程序),其次在客观题会考察一些stm3二、Cortex-M三、嵌入式、数电模电的一些基础知识
这个比赛最大的优势也是最大的缺点即是:考察范围有限
看完这篇文章而且掌握我所提到的东西,至少能拿省一。欢迎交流与分享,切勿商用哦。
全部童鞋必定会问到比赛时提供什么,这一点官网也没有作很好的说明,在这里我说明一下:
第八届和第九届的省赛都是只提供竞赛板,不涉及扩展板内容
省赛提供资料图:
CT117E(就是竞赛板)电路原理图和竞赛板使用说明
比赛时若是忘记板上资源对应的引脚,电路原理图用来查看对应引脚。
串口调试工具
若是考察到串口(UART),须要用调试工具接收和发送来验证程序
驱动及插件
FT2232驱动和CooCox驱动,这些都用不到,不要管它
数据手册
数据手册资料图:
这些数据手册各有各的用处,我认为最有用的是stm32f103rbt6.pdf,怎么看数据手册以及何时看后面会讲。
液晶驱动参考历程资料图:
一个已经写好的工程CT117E-LCD: 这个工程能够做为咱们进考场时测试板子及其屏幕是否正常工做, 也能够直接在这个工程上构建咱们的程序,省略了咱们从新建工程的时间(我选择从新构建工程) lcd驱动文件:一个.c源文件和两个.h头文件,这三个就是lcd的驱动文件,后面会讲到如何使用它们
i2c参考程序
只有i2c.c和i2c.h两个文件,只在E2PROM和陀螺仪的时候能用到,后面会讲如何使用
STM32固件库v3.5
固件库资料图:
这个固件库十分关键,一方面要用来构建工程,一方面又能利用库函数和里面的Example快速编写驱动
在硬件上:竞赛板+扩展板
国赛在省赛提供的全部资料的基础上,增长了:
扩展板的相关数据手册和电路原理图
DS18B20和DHT11的驱动
只有.c和.h文件,没有工程和demo
万里长城第一步,先把stm32工程给创建起来,构建工程须要用到比赛提供的STM32固件库和keil
keil的安装请参照网上的各类教程,多的数不清~
这里须要特殊说明一下,比赛时提供的是keil4,而若是你日常训练使用keil5的话主要有两点不一样:
综上所述,我建议在日常联系就跟比赛时同样采用keil4,避免一些没必要要的麻烦。
能够看到我在C盘目录下创建了一个名叫test的文件夹
能够看到在文件夹“STM32F10x_StdPeriph_Lib_V3.5.0”的内容有:
最终CORE文件夹内的文件有:
最终FWLIB文件夹内的文件有:
最终USER文件夹内的文件有:
点击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里左侧的项目目录已经发生了变化
写下如下代码:
int main() { while(1) { } }
写过嵌入式代码的童鞋应该对这两行代码并不陌生,main.c 文件最终样式:
点击Target Options…,进入工程设置的组件:
在Output中点击"Select Folder for Objects…":
选择OUTPUT文件夹:
在C/C++中的Define里填写
USE_STDPERIPH_DRIVER,STM32F10X_MD
填写后效果为:
随后点击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):
你能够选择直接使用比赛时提供的液晶驱动例程,也能够选择本身构建工程。
我建议本身构建工程,我的认为液晶驱动例程的结构不清晰,本身构建的用着顺手。
我把全部驱动文件都存在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"来使用。具体的驱动代码在后面会讲。
我建议每次练习一个题目都从新构建一遍工程,毕竟孰能生巧,在比赛时最好不要在准备工做上出问题。
LED驱动其实就是单纯对GPIO进行操做。
可是这里加入了一个74HC573做为数据锁存器,因此每次操做须要给PD一个降低沿。
LED | STM32 |
---|---|
LED0 ~ 7 | PC8 ~1 5 |
LE | PD2 |
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
在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);
LED的初始化驱动代码其实就是初始化了GPIO,设置对应引脚为推挽输出
LED的控制驱动代码就是对GPIO进行高低电平的输出
全是调用库函数来实现的,咱们能够没必要彻底背过每一个库函数的具体写法,直接从相应的库的Example里复制粘贴便可,大大减小比赛时间。
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->GPIO->IOToggle"文件夹下,打开"main.c"
能够看到里面有基本上全部LED初始化驱动所需的代码,直接复制,改改对应引脚和对应使能时钟便可:
设置引脚高低电平的函数能够在keil左侧的Project里找到"stm32f10x_gpio.h",打开后拉到文件最底找到:
若是快速找到这些代码,就能够大大节省敲代码所花费的时间,把更多的时间留在逻辑层,增长得奖的可能性
我我的的原则就是,能复制粘贴解决的事就不本身敲,由于这是比赛,有时间限制,要让本身得奖可能最大化
为何要先说延时函数呢?原本是要说按键驱动的,而按键我使用外部中断,利用延时函数来消抖,仍是先说一下延时函数。
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文件底部:
由于以后要用到咱们的延时函数来打断外部中断对按键进行消抖,因此要更改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():
先在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"里找到
仍是那句话能复制粘贴就复制粘贴,省时间还减小出错率
这四个按键有两个方法进行驱动
我我的建议使用外部中断,由于咱们的嵌入式系统还要干其它工做,尤为若是要捕获PWM,刷新屏幕或者有时间间隔很小的中断任务要执行,循环扫描就不那么好使了。
但也不是绝对的,仍是要根据具体需求具体分析,可是使用外部中断来做为按键驱动目前来看还没发现什么缺点。
因此我强烈建议使用外部中断,而且我下面也会讲怎么用比赛提供的库函数Example简化咱们写驱动的任务。
KEY | STM32 |
---|---|
KEY1 | PA0 |
KEY2 | PA8 |
KEY3 | PB1 |
KEY4 | PB2 |
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
在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); } }
跟LED驱动同样,KEY驱动也能够在对应的库函数Example中找到大量咱们须要的代码,减小写驱动的时间。
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->EXTI->EXTI_Config"文件夹下,打开"main.c":
再打开"EXTI_Config"下的"stm32f10x_it.c":
有大量能够复制粘贴的代码,修改和增长咱们须要的部分便可。
一共是三个文件:
在main函数中调用:
int main() { STM3210B_LCD_Init(); delay_ms(500); LCD_Clear(White); LCD_SetTextColor(Blue); LCD_SetBackColor(White); }
这几个函数都能在lcd.h中找到,直接复制粘贴过来就行,不用死记硬背:
我推荐的作法是写一个window()函数,在主函数main()的while(1)循环中每隔一段时间执行一次(即刷新屏幕)
好比我要完成这样一个任务:
我就这样写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); } }
主要仍是要灵活运用
再好比,比赛中常常让你高亮显示某一行,高亮就是把蓝色的字设置为绿色或者其余亮一点的颜色的字
那么咱们再规定一个任务:
咱们设置一个全局变量,默认是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); }
这里请理解按键控制全局变量进行改变,显示函数根据变量来改变屏幕的输出
CT117E使用UART2链接外部串行接口,便可以直接使用UART2与上位机通讯。
总之就是初始化的时候初始化UART2,接受和发送的时候使用UART2便可
接收的时候使用中断接收,发送的时候主动发送并查询
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
首先在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); } }
若是要接收字符串,给出一个任务:
能够这么作:
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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->USART->Interrupt"文件夹下,打开"main.c":
能够找到对应UART的初始化代码,还能够找到引脚初始化,中断初始化等等的代码:
把接收引脚改为PA3,发送引脚改为PA2便可,中断IRQChannel改为USART2_IRQn,再使能对应的时钟便可。
再打开同目录下的"stm32f10x_it.c"能够找到接收中断函数相关代码:
咱们使用查询的方式发送,因此不须要中断发送的相关代码。
对E2PROM的考察在近几年频繁出现,主要是E2PROM能够完成掉电存储的功能,这个东西几乎是必考,因此:
在我这篇博文里有讲如何使用IO口模拟I2C协议:
https://blog.csdn.net/zach_z/article/details/75331275
E2PROM | STM32 |
---|---|
SCL | PB6 |
SDA | PB7 |
直接把比赛提供的"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; }
这里须要注意,在每次执行完读或者写以后须要加一个延时函数(我延时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++;
比赛时不只仅会让读写一个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; }
首先要在main函数初始化时对i2c进行一个初始化,其次使用读函数读出咱们须要的数据,若是要写就直接写数据便可。
咱们规定这样一个任务:
#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); } }
RTC在基础板也可能做为一个考察点,好比第九届省赛就让作个电子时钟,,其实也能够用其余计时方法,可是为了比赛准备更加充分,仍是要至少可以快速编写RTC驱动。
规定这样一个任务:
这样完成任务:
建立"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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->RTC->LSI_Calib"文件夹下,打开"main.c":
直接复制粘贴RTC_Configuration()部分,去掉最后两句代码,加上咱们的计数值设定便可。
在相同目录下,打开"stm32f10x_it.c"还能够看到RTC中断函数相关代码,修改修改直接使用便可:
另外RTC_SetCounter()和RTC_GetCounter()也能够在库函数文件"stm32f10x_rtc.h"里找到,若是忘记能够在那里找:
定时器中断是很是有用的,不管是在省赛仍是在国赛,若是你想让你的嵌入式系统更加完美,运行效果更好,使用定时器中断必定会起到不少做用
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
假设咱们设计:每隔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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->TimeBase"文件夹下,打开"main.c":
对ADC使用是考察的重点,第九届国赛和第八届国赛都考察到了,分值占比极大。
若是没有用到扩展板,AD采样应该是会经过基础板上那个电位器R37来调节电压进行捕获考察ADC的使用
能够根据数据手册看到,PB0对应着ADC的Channel_8
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
好比每隔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); } }
两路AD采样确定是用到扩展板了,扩展板的
扩展板使用跳线帽将PA4和AO1相连,PA5和AO2相连,因此通道4对经过电位器PR5调节的输出电压进行采样;通道5对经过电位器PR6调节的输出电压进行采样
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; }
规定任务为:
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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->ADC->ADC1_DMA"文件夹下,打开"main.c":
获取adc值函数中用到的ADC库函数都能在"stm32f10x_adc.h"中找到,记不住就能够去复制粘贴
PWM是最最最重要的部分,咱们训练的目标在于
这里必定要玩的很是清楚和熟练,PWM的工做原理我就不介绍了,自行学习
咱们要根据不一样的题目需求来选择不一样方式来捕获pwm或者输出pwm
引脚 | 对应时钟通道 | 扩展版说明 |
---|---|---|
PA1 | TIM2_CH2 | 能够调节输入PWM的频率 |
PA2 | TIM2_CH3 | 能够调节输入PWM的频率 |
PA6 | TIM3_CH1 | 能够调节输入PWM的占空比 |
PA7 | TIM3_CH2 | 能够调节输入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); }
好比咱们要求:
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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Output"文件夹下,打开"main.c":
找到能用的复制粘贴过来就行
这种状况就能够直接使用比较输出
规定任务:
则咱们这样写比较输出的驱动:
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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle"文件夹下,打开"main.c":
再打开同目录下的"stm32f10x_it.h":
若是只捕获一路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; } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle"文件夹下,"main.c"和"stm32f10x_it.c"分别有着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); } }
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->InputCapture"文件夹下,"main.c"和"stm32f10x_it.c"分别有着输入捕获模式初始化和中断服务程序相关代码:
扩展板的8个按键根据AD采样到相应引脚的电压来判断是哪一按键
前面ADC驱动已经讲过怎么写了,就跳过ADC的部分
扩展版按键经过跳线帽将PA5与AKEY相连
这8个按键须要注意的地方:
直接贴出扩展版按键驱动:
#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(); ... } }
数码管在官方给的声明通知中说只考察静态,无论考不考,咱们准备好,知道怎么写驱动总归是不亏的。
PA1~3用跳线帽跟P3上的对应引脚相连便可
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
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); } }
DHT11是一个温湿度传感器
由于比赛提供dht11的驱动文件,因此直接把.c和.h文件添加到咱们的HARDWARE文件夹下,而且添加进工程便可。
PA7经过跳线帽与P3上对应引脚相连就能够驱动dht11温湿度传感器了
规定一个任务
#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); } }
这个东西通常不会考察,就像蜂鸣器同样,考察蜂鸣器考场会乱成一锅粥,这个东西也要拿着板子转来转去,并且它占用引脚也多,可是保不齐这几年把扩展板玩的没什么可考察的了,就出个三轴传感器的题目,最好仍是要掌握一下驱动怎么写。
这个外设的通讯协议也是I2C跟咱们以前说到的E2PROM同样,因此咱们就轻车熟路了。
主要是如下两点:
修改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); } }
这个也没什么讲的,要考察也不大可能考察到这里,看一眼驱动就好了
一共有两个光敏电阻,一个直接读引脚高低判断光强,一个是根据输入电压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); } }
片内资源是能够经过数据手册查找到的,具体能够打开考场提供数据手册"stm32f103rbt6.pdf"查看:
可能考到的基本都在数据手册能找到,比赛时应该是提供英文版手册,我建议在比赛前把手册都看一遍,至少能知道对应知识点在哪里找。
STM32F103xx加强型产品内置嵌套的向量式中断控制器,可以处理多达43个可屏蔽中断通道(不包括 16个Cortex™-M3的中断线)和16个优先级。
记住16个优先级,5个优先级分组
外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每一个中断线均可以独立地配置 它的触发事件(上升沿或降低沿或双边沿),并可以单独地被屏蔽;有一个挂起寄存器维持全部中断请 求的状态。EXTI能够检测到脉冲宽度小于内部APB2的时钟周期。多达80个通用I/O口链接到16个外 部中断线。
统时钟的选择是在启动时进行,复位时内部8MHz的RC振荡器被选为默认的CPU时钟,随后能够 选择外部的、具失效监控的4~16MHz时钟;
当检测到外部时钟失效时,它将被隔离,系统将自动地 切换到内部的RC振荡器,若是使能了中断,软件能够接收到相应的中断。
一样,在须要时能够采起 对PLL时钟彻底的中断管理(如当一个间接使用的外部振荡器失效时)。
多个预分频器用于配置AHB的频率、高速APB(APB2)和低速APB(APB1)区域。
AHB和高速APB的最 高频率是72MHz,低速APB的最高频率为36MHz。
参考如图的时钟驱动框图:
最重要的是要经过时钟框图找到时钟源和相应设备的关系:
好比40KHz的RC振荡器HSI做为独立看门狗(IWDG)的时钟源:
DMA基本在客观题内算是必考,由于DMA是嵌入式里一个重要的传输手段,而主观题又很差考察,因此必定会在客观题上考察DMA的相应知识点
灵活的7路通用DMA能够管理存储器到存储器、设备到存储器和存储器到设备的数据传输;DMA控 制器支持环形缓冲区的管理,避免了控制器传输到达缓冲区结尾时所产生的中断。
每一个通道都有专门的硬件DMA请求逻辑,同时能够由软件触发每一个通道;传输的长度、传输的源地 址和目标地址均可以经过软件单独设置。
DMA能够用于主要的外设:SPI、I 2 C、USART,通用、基本和高级控制定时器TIMx和ADC。
STM32 的 DMA 有如下一些特性:
独立的看门狗是基于一个12位的递减计数器和一个8位的预分频器,它由一个内部独立的40kHz的RC 振荡器提供时钟;
由于这个RC振荡器独立于主时钟,因此它可运行于停机和待机模式。它能够被当 成看门狗用于在发生问题时复位整个系统,或做为一个自由定时器为应用程序提供超时管理。
经过 选项字节能够配置成是软件或硬件启动看门狗。在调试模式下,计数器能够被冻结。
窗口看门狗内有一个7位的递减计数器,并能够设置成自由运行。它能够被当成看门狗用于在发生问 题时复位整个系统。
它由主时钟驱动,具备早期预警中断功能;在调试模式下,计数器能够被冻结
客观题没办法速成,仍是在于日常的积累,咱们只能有针对性的去了解一些方面
这些知识掌握的越多越好
能够外接的引脚一共只有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 / 占空比可调脉冲 / 温湿度传感器 |
整体路线:
若是你看了我上面对每一个驱动代码的编写,你就能知道其实大部分的代码咱们都不须要本身敲。
比赛提供的库函数的Example中就有着咱们基本须要驱动的代码,咱们只须要更改到咱们须要的(时钟、引脚等等)便可。
一些函数若是忘记怎么写,对应库函数的头文件也能够找到。
但愿每次从新作一道主观题时,都能从从新构建工程->从新编写每一个驱动代码->写逻辑层上的代码
由于比赛时和日常作东西不同,即使你认为本身原理都懂、驱动都知道怎么写,若是没有作到很是熟练,不免会出现小失误,而小失误有可能会耽误大量的时间
因此既然选择了参加比赛,那么咱们就指望准备约充分越好,对工程创建约熟练越好、基础驱动编写越快越好
若是你是小白,那么必定是越早开始备赛越好,一方面学习stm32的应用知识,另外一方面不断地练习完成一个嵌入式系统
若是你有必定的5一、stm32或是其余嵌入式设备的开发基础,那么根据你的学习能力来分配备赛时间
我省赛前一共用了两周时间大概是这么分配的:
刚开始作模拟题确定会很慢,七、8个小时才能完成任务,训练两三届的题就会愈来愈顺手了
比赛时候总有一些同窗提早离场,也不知道是所有作出信心满满仍是作不出来直接放弃
我认为既然已经选择了来比赛,比赛前也作了那么多的准备,就应该尽最大的努力去完成比赛
若是已经作出来了程序的全部任务,觉着比赛稳了:
总之,准备几个星期就为了这5个小时,提早走总有些得不偿失
省赛每一个地方不同,可能不会让提早进场,我就假设5个小时的时间:
国赛通常提早半小时能够进赛场,排队进,早排队的大概能比最后的早进十几分钟,因此尽可能早去多争取十几分钟:
再次说明一下离结束半小时提交的重要性:
全部比赛基本上都会碰到问题:ACM、电赛、咱们参加的蓝桥杯都同样,都会碰到或大或小的问题
要明确一点:
碰到问题必定要用本身掌握的知识和训练的经验去一步步解决,冷静分析,不要放弃
根据实例来理解我写的各个驱动的使用方法,代码能够到“山不高海不深”公众号中回复“第八届嵌入式国赛代码”
PS:回复“第五届嵌入式代码”还能够看看第五届的试题完成代码
——西安邮电大学 张凯捷