本文是我参加蓝桥杯嵌入式比赛后的一些心得体会和一些自己总结的驱动代码,希望能给以后参加蓝桥杯嵌入式的同学带来一些帮助。
本文没有经过校对,如有错误还请包涵,欢迎大家交流和指正,转载请注明出处。
一、 总述
自己的情况:
我参加的是第十届蓝桥杯嵌入式比赛
省赛备赛两周(平均每天花费4——6小时),最后省一进入国赛
国赛备赛一周半(每天晚上写一个省赛或者国赛的项目),最后国一
之前有过学习过stm32的可以快速上手(看到这里不要急,拿不拿奖与有没有stm32使用经历没有太大关系)
再谈谈对蓝桥杯嵌入式比赛的认识:
简介套话不说,这个比赛我个人认为得奖很容易,但想拿国一及以上就有些难度了,需要一定的训练加一定的运气。比如我当时需要自己写18b20的读取函数,刚好我背了,然后就幸运的写了出来,官方他只给底层驱动,比如他给你i2c的读函数、写函数、停止函数、开始函数,当时他不给你读写24c02的函数,这个时候就只有你自己写24c02的读写函数了
比赛考察范围有限且基本上都是套路,有限的知识和套路全部掌握了就能拿到国二,我也会在接下来的文章里把所有要掌握的点全部给大家梳理清楚
这个比赛主要针对的是电子工程类专业学生和计科学生,如果不是这些专业的学生就需要补充一些基础知识(C语言、微机原理、数电模电…)
比赛还是主要面向双非学校学生,主要考察对stm32的使用(毕竟占总成绩70%的程序题就是写个小程序),其次在客观题会考察一些stm32、Cortex-M3、嵌入式、数电模电的一些基础知识(尤其是Cortex-M3内核的东西)
我将会总结什么:
我认为必须掌握的驱动及其运用
怎么通过比赛提供的资料迅速完成我们的程序
我对考察点的个人心得,以及怎么分配我们的备赛时间
客观题的准备
以及一系列客观题
PS:
这个比赛最大的优点也是最大的缺点便是:考察范围有限
对了,这个嵌入式开发软件只能是keil4,keil5是不能下载的。所以大家必须装keil4,我用的keil4.73,比赛版本也好像是4.73。
keil4.73下载链接:点击进入
看完这篇文章并且掌握我所提到的东西,至少能拿省一。欢迎交流与分享,切勿商用哦。
二、比赛提供
所有童鞋一定会问到比赛时提供什么,这一点官网也没有做很好的说明,在这里我说明一下:
2.1 省赛提供
2.1.1 省赛硬件方面
省赛都是只提供竞赛板,不涉及扩展板内容
2.1.2 省赛软件方面
省赛提供资料图:
下载链接:点击下载
2.1.2.1 CT117E(就是竞赛板)电路原理图和竞赛板使用说明
比赛时如果忘记板上资源对应的引脚,电路原理图用来查看对应引脚。
2.1.2.2 串口调试工具
开始进入考场后第一件事就是检查一下板载的FT2232插上后是否可用,是否可以下载程序,是否可以使用串口,尤其是是否可以下载 程序
2.1.2.3 数据手册资料图:
这些数据手册各有各的用处,我认为最有用的stm32f103rbt6.pdf,怎么看数据手册以及什么时候看后面会讲。别的常用的就需要背下来比如24c02,我当时省赛国赛都用了
2.1.2.4 液晶驱动参考例程
液晶驱动参考历程资料图:
- 一个已经写好的工程CT117E-LCD: 这个工程可以作为我们进考场时测试板子及其屏幕是否正常工作,也可以直接在这个工程上构建我们的程序,省略了我们重新建工程的时间(我选择重新构建工程),所以千万不要做傻事,来自己新建工程,花时间又划不来。
- lcd驱动文件:一个.c源文件和两个.h头文件,这三个就是lcd的驱动文件,后面会讲到如何使用它们
2.1.2.5 i2c参考程序
只有i2c.c和i2c.h两个文件,只在E2PROM和陀螺仪的时候能用到,后面会讲如何使用
2.1.2.6 STM32固件库v3.5
固件库资料图:
这个固件库十分关键,只要能利用好里面的库函数和里面的Example就可快速编写驱动
2.2 国赛提供
在硬件上:竞赛板+扩展板
国赛在省赛提供的所有资料的基础上,增加了:
扩展板的相关数据手册和电路原理图
DS18B20和DHT11的驱动
- 只有.c和.h文件,没有工程和demo,并且没有读取函数
三、快速编写必备驱动
首先给大家说说,这个比赛并不看你程序里面的布局,所以我就把所有程序都放在main里面的,这样节省时间,只要把每个函数都封装好,也不会出现把自己搞晕啥的,尤其是国赛,题量特别大,我做到最后都还有一点点小问题没解决,所以我们必须的节约时间,我每个模块的函数都会放在main()函数之上,这样我们就省去了定义这个步骤。可以让我们的开发速度更快。
下面我开始写借助固件库快速写驱动办法,我们所有的参考文件都来自STM32固件库v3.5\stm32f10x_stdperiph_lib\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples目录下面
3.1LED驱动
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->GPIO->IOToggle"文件夹下,打开"main.c"
/***********************************************
*****************LED****************************
************************************************/
void led_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD, ENABLE);
/* Configure PD0 and PD2 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = 0XFF00;//stm内部就是将io按位操作的
//这样写就相当于选中GPIO_Pin 8-15
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = 0X0004;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,0XFF00); //Pc 8-15
GPIO_SetBits(GPIOD,0X0004); //pd2
GPIO_ResetBits(GPIOD,0x0004); //pd2
}
u16 led_data=0xff00;//保存上次的灯的状态
void led_one(u8 led,_Bool Flag)
{
GPIO_SetBits(GPIOC,led_data);
led=led+7;
if(Flag) GPIO_ResetBits(GPIOC,1<<led);
else GPIO_SetBits(GPIOC,1<<led);
GPIO_SetBits(GPIOD,0X0004);
GPIO_ResetBits(GPIOD,0x0004);
led_data=GPIO_ReadOutputData(GPIOC);
}
3.2key按键驱动
key按键的驱动有两种办法,
- 外部中断
- 循环扫描
这里我推荐用外部中断,因为响应快,但是最好两个都学,不然万一不能用外部中断就凉凉
3.2.1外部中断法
这里需要知道32的外部中断的服务函数是怎么分配的
这里exti0-4都有独立的中断服务函数,在startup_stm32f10x_md.s可以看到
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
exti9-5是一个合并的中断服务函数,在这个中断服务函数里面就需要自己判断是哪一外部中断引起的响应事件
exti15-10又是一个合并的中断服务函数,也需要判断响应的是哪个中断服务函数
然后这里中断服务函数跟io是这样对应的
GPIO | 中断服务函数 |
---|---|
GPIO0 | exti0 |
GPIO1 | exti1 |
… | … |
GPIO5 | exti9-5 |
GPIO6 | exti9-5 |
… | exti9-5 |
GPIO10 | exti15-10 |
GPIO11 | exti15-10 |
… | exti15-10 |
GPIO15 | exti15-10 |
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->EXTI->EXTI_Config"文件夹下,打开"main.c":
再打开"EXTI_Config"下的"stm32f10x_it.c":
/*************************************************************
***************************EXTI********************************
**************************************************************/
u8 key_value=0;
void EXTI0_Config(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable GPIOA clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);
/* Configure PA.00 pin as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
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);
/* Enable AFIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/* Connect EXTI0 Line to PA.00 pin */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
/* Configure EXTI0 line */
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);
/* Enable and set EXTI0 Interrupt to the lowest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Connect EXTI8 Line to PG.08 pin */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);
/* Configure EXTI8 line */
EXTI_InitStructure.EXTI_Line = EXTI_Line8;
EXTI_Init(&EXTI_InitStructure);
/* Enable and set EXTI9_5 Interrupt to the lowest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_Init(&NVIC_InitStructure);
/* Connect EXTI8 Line to PG.08 pin */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
/* Configure EXTI8 line */
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_Init(&EXTI_InitStructure);
/* Enable and set EXTI9_5 Interrupt to the lowest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_Init(&NVIC_InitStructure);
/* Connect EXTI8 Line to PG.08 pin */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource2);
/* Configure EXTI8 line */
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_Init(&EXTI_InitStructure);
/* Enable and set EXTI9_5 Interrupt to the lowest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
key_value=1;
/* Clear the EXTI line 0 pending bit */
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
/**
* @brief This function handles External lines 9 to 5 interrupt request.
* @param None
* @retval None
*/
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
key_value=2;
/* Clear the EXTI line 8 pending bit */
EXTI_ClearITPendingBit(EXTI_Line8);
}
}
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
key_value=3;
/* Clear the EXTI line 0 pending bit */
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2) != RESET)
{
key_value=4;
/* Clear the EXTI line 0 pending bit */
EXTI_ClearITPendingBit(EXTI_Line2);
}
}
3.2.2key按键循环扫描
这个就需要自己写了,这个是我自己写的,很简单,写起来也快。
void key_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB ,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t key_data_out=0;
void key_data()
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0)
{
Delay_Ms(10);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0)
{
key_data_out=1;
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0);
}
}
else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)==0)
{
Delay_Ms(10);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)==0)
{
key_data_out=2;
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)==0);
}
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
Delay_Ms(10);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
key_data_out=3;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
}
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)==0)
{
Delay_Ms(10);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)==0)
{
key_data_out=4;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)==0);
}
}
else key_data_out=0;
}
3.3串口驱动
初始化如果有问题先看有没有打开端口,串口有时候如果不行就把初始化位置移一下,
Project->STM32F10x_StdPeriph_Examples->USART->Interrupt"文件夹下,打开"main.c":
把接收引脚改成PA3,发送引脚改成PA2即可,中断IRQChannel改成USART2_IRQn,再使能对应的时钟即可。
再打开同目录下的"stm32f10x_it.c"可以找到接收中断函数相关代码:
然后再打开USART->Printf目录下的main.c函数,可找到发送函数
/*********************************************************************
************************串口*****************************************
******************************************************************/
void usart_Init()
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,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);
}
/*更改的是USART\printf\main里面的PUTCHAR_PROTOTYPE函数*/
void usart_data_while(u8* ch)
{
while(*ch)
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2, (uint8_t) *ch++);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET)
{}
}
}
uint8_t RxBuffer1[20];
__IO uint8_t RxCounter1;
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
/* Read one byte from the receive data register */
RxBuffer1[RxCounter1++] = USART_ReceiveData(USART2);
if(RxCounter1 == 20)
{
RxCounter1=0;
}
}
}
3.4 E2PROM的驱动程序
3.4.1 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();
Delay_Ms(3);
}
u8 x24_read(u8 add)
{
u8 data;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
data=I2CReceiveByte();
I2CSendAck();
Delay_Ms(3);
return data;
}
这里需要注意,在每次执行完读或者写之后需要加一个延时函数(我延时3ms),因为MCU内部执行速度太快,而E2PROM外设跟不上内部时钟频率。MCU的执行速度太快,而外设反应跟不上,就会导致data可能还没有取得E2PROM里的数据的时候data就开始自增,导致程序预期结果和实际执行结果不一样。
3.4.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;
}
3.4.3 使用E2PROM驱动
首先要在main函数初始化时对i2c进行一个初始化,其次使用读函数读出我们需要的数据,如果要写就直接写数据即可。
#include "i2c.h"
u32 test_int=0;
int main()
{
i2c_init();
if(x24_read_int(0x55)==0x55)//判断是否第一次写入,如果是第一次,就写入
{
x24_write(0x55,0x55);
test_int=x24_read_int(0x01);
}
while(1)
{
...
}
}
3.5 利用RTC完成时钟
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->RTC->LSI_Calib"文件夹下,打开"main.c":
直接复制粘贴RTC_Configuration()部分,最后两句话记得不要,是备份啥的,我们没有电池,所以没有备份,
void RTC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* Enable the RTC Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 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();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
}
获取时间参考RTC\Calendar这个文件里面的。
我把所有的printf函数都删除了,我们不需要发到串口,而且printf构造也麻烦。
/**
* @brief Adjusts time.
* @param None
* @retval None
*/
void Time_Adjust(void)//设置初始时间
{
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Change the current time */
RTC_SetCounter(0);//注意,这里改过,这里是设置初始时间的
//比如我们设置23.50.00可写23*3600+50*60+0
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
uint32_t THH = 0, TMM = 0, TSS = 0;
/**
* @brief Displays the current time.
* @param TimeVar: RTC counter value.
* @retval None
*/
void Time_Display(uint32_t TimeVar)
{
/* Reset RTC Counter when Time is 23:59:59 */
if (RTC_GetCounter() == 0x0001517F)
{
RTC_SetCounter(0x0);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
/* Compute hours */
THH = TimeVar / 3600;
/* Compute minutes */
TMM = (TimeVar % 3600) / 60;
/* Compute seconds */
TSS = (TimeVar % 3600) % 60;
}
__IO uint32_t TimeDisplay = 0;//这个在当前函数里面有定义
/**
* @brief Shows the current time (HH:MM:SS) on the Hyperterminal.
* @param None
* @retval None
*/
void Time_Show(void)
{
/* If 1s has been elapsed */
//这里删除了while,不能让程序停在这儿
if (TimeDisplay == 1)
{
/* Display current time */
Time_Display(RTC_GetCounter());
TimeDisplay = 0;
}
}
调用初始化RTC_Configuration之后,循环调用Time_Show就可实时更新时间了。Time_Adjust可设置初始时间
3.6定时器中断
这里有必要说一下,定时器中断一般用不上,需要定时中断的话建议利用用SysTick中断服务函数SysTick_Handler,在stm32f10x_it.c里面,直接定义个全局变量,然后在SysTick中断服务函数里面让变量一直加加,这样的话变量就会1ms加一次,通过加的次数就可得出准确时间。用完之后记得清0,方便下次使用。
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->TimeBase"文件夹下,打开"main.c":
- 取用基础时钟设置的代码
- 取用中断配置的代码
- 中断设置改为TIM_IT_Update
- 增加时钟使能
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);
}
}
3.7 ADC
对ADC使用是考察的重点,我省赛国赛都考察到了,分值占比极大。尤其是板载的滑动电阻的那路ADC。基本百分之百考
3.7.1 一路AD采样
如果没有用到扩展板,AD采样应该是会通过基础板上那个电位器R37来调节电压进行捕获考察ADC的使用
3.7.1.1 一路AD采样的硬件连接
3.7.1.2 多路AD采样的软件驱动(也可用于一路)
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->ADC->ADC1_DMA"文件夹下,打开"main.c":
因为我不需要DMA去读值,所以我把所有的关于DMA的地方都删了,然后我把RCC_Configuration这些函数都加上了_adc表示是adc的函数
/************************************************************
***************************ADC*******************************
*************************************************************/
void RCC_Configuration_adc(void)
{
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
/* ADCCLK = PCLK2/2 */
RCC_ADCCLKConfig(RCC_PCLK2_Div2);
#else
/* ADCCLK = PCLK2/4 */
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
#endif
/* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);
}
/**
* @brief Configures the different GPIO ports.
* @param None
* @retval None
*/
void GPIO_Configuration_adc(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PC.04 (ADC Channel14) as analog input -------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//改成自己要用到io
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//改成自己要用到io
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
#define ADC1_DR_Address ((uint32_t)0x4001244C)
void adc_init()
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration_adc();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration_adc();
/* ADC1 configuration ------------------------------------------------------*/
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;//这里可以设置成1到16,带边同时转换几路,因为我们每次只转换一路,所以不改。
ADC_Init(ADC1, &ADC_InitStructure);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);//上面一句话没了,上面那句话是选择需要获取的ADC通道,我们因为是需要获取不同通道的,所以我们将它用在获取函数中
/* Enable ADC1 reset calibration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
//这里ADC_SoftwareStartConvCmd(ADC1, ENABLE);我删了,这函数是开启adc转换的,我们将它放在了获取函数里,设置好一次通道获取一次
}
u16 adc_data(u8 ch)//新加,第一句是选择通道(初始化里面的),第二句开始转换(初始化里面的),
第三句等待转换结束,背下来,官方库文件在ADC.H里面,第四句在3ADCs_DMA中断函数获取函数里面
{
/* ADC1 regular channel14 configuration */
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5);
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
初始化好之后,调用adc_data就可以返回adc采集的值,传进去的变量是哪个通道,比如通道8,就写8就行
3.8PWM
PWM是最最最重要的部分,我们训练的目标在于
- 输出两路占空比可调,频率可调的PWM
- 捕获两路PWM的频率和占空比
这里一定要玩的非常清楚和熟练,PWM的工作原理我就不介绍了,自行学习
我们要根据不同的题目需求来选择不同方式来捕获pwm或者输出pwm
3.8.1捕获两路路PWM的频率(我写的是两路的,一路更简单,)
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->InputCapture"文件夹下,"main.c"和"stm32f10x_it.c"分别有着输入捕获模式初始化和中断服务程序相关代码:
变量位置
/*********************************************************************
************************输入捕获*****************************************
******************************************************************/
参考InputCapture文件,记得中断读值函数要分开
void RCC_Configuration_tim_input(void)
{
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
/**
* @brief Configure the GPIOD Pins.
* @param None
* @retval None
*/
void GPIO_Configuration_tim_input(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* TIM2 channel 2 pin (PA.07) configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief Configure the nested vectored interrupt controller.
* @param None
* @retval None
*/
void NVIC_Configuration_tim_input(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the TIM2 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void tim_input_init()
{
TIM_ICInitTypeDef TIM_ICInitStructure;
/* System Clocks Configuration */
RCC_Configuration_tim_input();
/* NVIC configuration */
NVIC_Configuration_tim_input();
/* Configure the GPIO ports */
GPIO_Configuration_tim_input();
/* TIM2 configuration: Input Capture mode ---------------------
The external signal is connected to TIM2 CH2 pin (PA.07)
The Rising edge is used as active edge,
The TIM2 CCR2 is used to compute the frequency value
------------------------------------------------------------ */
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(TIM2, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
/* TIM enable counter */
TIM_Cmd(TIM2, ENABLE);
/* Enable the CC2 Interrupt Request */
TIM_ITConfig(TIM2, TIM_IT_CC2|TIM_IT_CC3, ENABLE);
}
__IO uint16_t IC3ReadValue11 = 0, IC3ReadValue21 = 0;
__IO uint16_t CaptureNumber1 = 0;
__IO uint32_t Capture1 = 0;
__IO uint32_t TIM2Freq1 = 0;
__IO uint16_t IC3ReadValue12 = 0, IC3ReadValue22 = 0;
__IO uint16_t CaptureNumber2 = 0;
__IO uint32_t Capture2 = 0;
__IO uint32_t TIM2Freq2 = 0;
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET)
{
/* Clear TIM2 Capture compare interrupt pending bit */
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
if(CaptureNumber1 == 0)
{
/* Get the Input Capture value */
IC3ReadValue11 = TIM_GetCapture2(TIM2);
CaptureNumber1 = 1;
}
else if(CaptureNumber1 == 1)
{
/* Get the Input Capture value */
IC3ReadValue21 = TIM_GetCapture2(TIM2);
/* Capture computation */
if (IC3ReadValue21 > IC3ReadValue11)
{
Capture1 = (IC3ReadValue21 - IC3ReadValue11);
}
else
{
Capture1 = ((0xFFFF - IC3ReadValue11) + IC3ReadValue21);
}
/* Frequency computation */
TIM2Freq1 = (uint32_t) SystemCoreClock / Capture1;
CaptureNumber1 = 0;
}
}
if(TIM_GetITStatus(TIM2, TIM_IT_CC3) == SET)
{
/* Clear TIM2 Capture compare interrupt pending bit */
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
if(CaptureNumber2 == 0)
{
/* Get the Input Capture value */
IC3ReadValue12 = TIM_GetCapture3(TIM2);
CaptureNumber2 = 1;
}
else if(CaptureNumber2 == 1)
{
/* Get the Input Capture value */
IC3ReadValue22 = TIM_GetCapture3(TIM2);
/* Capture computation */
if (IC3ReadValue22 > IC3ReadValue12)
{
Capture2 = (IC3ReadValue22 - IC3ReadValue12);
}
else
{
Capture2 = ((0xFFFF - IC3ReadValue12) + IC3ReadValue22);
}
/* Frequency computation */
TIM2Freq2 = (uint32_t) SystemCoreClock / Capture2;
CaptureNumber2 = 0;
}
}
}
调用初始化,然后读取TIM2Freq1、TIM2Freq2就是输入捕获值(频率),一路的直接删除另外一路的相关代码就好了。
3.8.2 输出两路占空比可调,频率可调的pwm
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle"文件夹下,打开"main.c":
main函数整个
变量的位置
void RCC_Configuration_pwm(void)
{
/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
}
/**
* @brief Configure the TIM3 Ouput Channels.
* @param None
* @retval None
*/
void GPIO_Configuration_pwm(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA Configuration:TIM3 Channel1, 2, 3 and 4 as alternate function push-pull */
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);
}
void NVIC_Configuration_pwm(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the TIM3 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void pwm_out_init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t CCR1Val = 333;
uint16_t CCR2Val = 249;
uint16_t PrescalerValue = 0;
/*!< At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup
file (startup_stm32f10x_xx.s) before to branch to application main.
To reconfigure the default setting of SystemInit() function, refer to
system_stm32f10x.c file
*/
/* System Clocks Configuration */
RCC_Configuration_pwm();
/* GPIO Configuration */
GPIO_Configuration_pwm();
/* NVIC Configuration */
NVIC_Configuration_pwm();
/* -----------------------------------------------------------------------
TIM3 Configuration: generate 4 PWM signals with 4 different duty cycles:
The TIM3CLK frequency is set to SystemCoreClock (Hz), to get TIM3 counter
clock at 24 MHz the Prescaler is computed as following:
- Prescaler = (TIM3CLK / TIM3 counter clock) - 1
SystemCoreClock is set to 72 MHz for Low-density, Medium-density, High-density
and Connectivity line devices and to 24 MHz for Low-Density Value line and
Medium-Density Value line devices
The TIM3 is running at 36 KHz: TIM3 Frequency = TIM3 counter clock/(ARR + 1)
= 24 MHz / 666 = 36 KHz
TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR)* 100 = 50%
TIM3 Channel2 duty cycle = (TIM3_CCR2/ TIM3_ARR)* 100 = 37.5%
TIM3 Channel3 duty cycle = (TIM3_CCR3/ TIM3_ARR)* 100 = 25%
TIM3 Channel4 duty cycle = (TIM3_CCR4/ TIM3_ARR)* 100 = 12.5%
----------------------------------------------------------------------- */
/* Compute the prescaler value */
PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1Val;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);
/* Output Compare Toggle Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2Val;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);
/* TIM enable counter */
TIM_Cmd(TIM3, ENABLE);
/* TIM IT enable */
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
}
同目录下stm32f10x_it.c里面有中断服务函数
uint16_t capture = 0;
__IO uint16_t CCR1Val;//改变这个值就可以改变频率
__IO uint16_t CCR2Val;
void TIM3_IRQHandler(void)
{
/* TIM3_CH1 toggling with frequency = 366.2 Hz */
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
capture = TIM_GetCapture1(TIM3);
TIM_SetCompare1(TIM3, capture + CCR1Val );
}
/* TIM3_CH2 toggling with frequency = 732.4 Hz */
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
capture = TIM_GetCapture2(TIM3);
TIM_SetCompare2(TIM3, capture + CCR2Val);
}
}
同时TIM_SetCompare1(TIM3,12000);是改变占空比的。
3.8.3PWM输出频率固定改变占空比
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Output"文件夹下,打开"main.c":
main函数全部复制,然后去掉while
变量定义
io口初始化
void RCC_Configuration_pwm(void)
{
/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO, ENABLE);
}
/**
* @brief Configure the TIM3 Ouput Channels.
* @param None
* @retval None
*/
void GPIO_Configuration_pwm(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA Configuration:TIM3 Channel1, 2, 3 and 4 as alternate function push-pull */
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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void pwm_out_init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t CCR1_Val = 333;
uint16_t CCR2_Val = 249;
uint16_t CCR3_Val = 166;
uint16_t CCR4_Val = 83;
uint16_t PrescalerValue = 0;
/*!< At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup
file (startup_stm32f10x_xx.s) before to branch to application main.
To reconfigure the default setting of SystemInit() function, refer to
system_stm32f10x.c file
*/
/* System Clocks Configuration */
RCC_Configuration_pwm();
/* GPIO Configuration */
GPIO_Configuration_pwm();
/* -----------------------------------------------------------------------
TIM3 Configuration: generate 4 PWM signals with 4 different duty cycles:
The TIM3CLK frequency is set to SystemCoreClock (Hz), to get TIM3 counter
clock at 24 MHz the Prescaler is computed as following:
- Prescaler = (TIM3CLK / TIM3 counter clock) - 1
SystemCoreClock is set to 72 MHz for Low-density, Medium-density, High-density
and Connectivity line devices and to 24 MHz for Low-Density Value line and
Medium-Density Value line devices
The TIM3 is running at 1 KHz: TIM3 Frequency = TIM3 counter clock/(ARR + 1)
= 24 MHz / 24k = 1 KHz
TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR)* 100 = 50%
TIM3 Channel2 duty cycle = (TIM3_CCR2/ TIM3_ARR)* 100 = 37.5%
TIM3 Channel3 duty cycle = (TIM3_CCR3/ TIM3_ARR)* 100 = 25%
TIM3 Channel4 duty cycle = (TIM3_CCR4/ TIM3_ARR)* 100 = 12.5%
----------------------------------------------------------------------- */
/* Compute the prescaler value */
PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;//设置计数速度,当前24000000hz,
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 24000-1;//设置重装值,加到这个值就重装
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;//设置翻转值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);//初始化TIM_-CC1
/* PWM1 Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);//初始化TIM_-CC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
/* TIM3 enable counter */
TIM_Cmd(TIM3, ENABLE);
}
- 可以看到这里TIM2的计数频率是24m,重装载值为24k,证明我们的频率为 24Mhz/24k=1Khz
- 我们直接设置了TIM3的CH1和CH2和CH3和CH4为PWM输出模式1,以及输出极性为高
- 改变占空比函数TIM_SetCompare1(TIM3,12000);,前面的1改成2就是修改CH2的,后面的值就是占空比值,具体占空比需要用你设置的值除去重装载值然后乘以100%,如我的12000/24000*100%=50%。
这里我们初始化好,就可以改变占空比了,如果只需要一路就把别的路的相关函数以及变量给删除就好了
3.8.4捕获一路PWM的频率和占空比
在比赛提供的V3.5库的"Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Input"文件夹下,"main.c"和"stm32f10x_it.c"分别有着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即为频率
- 可以给时钟分频,但是在计算频率的时候就要除以分频数
3.9 扩展板的矩阵按键
扩展板的8个按键根据AD采样到相应引脚的电压来判断是哪一按键
前面ADC驱动已经讲过怎么写了,就跳过ADC的部分
扩展版按键通过跳线帽将PA5与AKEY相连
这8个按键需要注意的地方:
- 板子上电阻阻值跟原理图上的百分之百有偏差,因为世界上没有两个元件参数是一模一样的,所以直接开始试,从小到大开始一个一个按键试
- 8个按键试出来也花不了10分钟,这是我得出的解决办法
直接贴出扩展版按键驱动:
void adc_key_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);//启动ADC转换
while(!ADC_GetFlagStatus( ADC1, ADC_FLAG_EOC));//等待ADC转换结束
btn_tmp=ADC_GetConversionValue(ADC1);//获取adc值
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
}
}
3.10扩展板上面的数码管
3.10.1 数码管的硬件连接
首先啊,别说不会用上,我当时国赛的时候就用了,它让我用数码管显示温度,同时还需要用usart2发数据,但是,这两个模块的引脚刚好冲突,都是用的PA2,PA3,所以我当时写的可刺激了。
- PA1~3用跳线帽跟P3上的对应引脚相连即可
3.10.2 数码管的软件驱动
- 这里需要自己对数码管的段码特别熟悉,我当时就还比较熟悉,然后边测试,边编写段显示数组,基本上2分钟就可以搞定。
- 这里74hc595的通信方式是串转并,就是时间信号(595的11脚)控制串口数据走595的串口数据输入io(595的14脚)一位一位输入,我们板载3个595,所以我们一共需要输入24位数据。并且595是先写入的是高位,就是写入的第一位是给QH的,最后一位是给QA的,(当第一个595被写满8位后未更新的时候再次写入数据,595的整体数据就会往后移动一位,开始的最高位数据就会走QH`输出给下一片595)
- 595的数据更新引脚是12脚,上升沿时将串口获取的数据给并口输出。
#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)
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_Control();传入需要显示的数值就好了,比如seg_Control(1,2,3);,显示就是123。
3.11 ds18b20驱动的使用
ds18b20是一个温度传感器
因为比赛提供ds18b20的驱动文件,所以直接把.c和.h文件添加到我们的HARDWARE文件夹下,并且添加进工程。
这里需要注意,比赛不给读取函数,需要自己写,就是我下图圈的部分没有,这个没有啥好办法,背下来解决一切问题。我当时就是运气好,刚好记得大致时序,不然我就把自己玩死了。
这里我是不建议你花时间去看数据手册的,太花时间了。
这里最重要的就是数据拼接,一定要切记读出来的开始八位是高位。然后读出来的值需要乘以0.625得出温度值。
int ds18b20_read(void)
{
uint8_t val[2];
uint8_t i = 0;
int x = 0;
ow_reset();
ow_byte_wr(OW_SKIP_ROM);
ow_byte_wr(DS18B20_CONVERT);
delay_us(750000);
ow_reset();
ow_byte_wr( OW_SKIP_ROM );
ow_byte_wr ( DS18B20_READ );
for ( i=0 ;i<2; i++) {
val[i] = ow_byte_rd();
}
x = val[1];
x <<= 8;
x |= val[0];
x=(float)x*0.0625*10;//把温度值放大了十倍,就相当于保留一位小数
return x;
}
3.12 DHT11的使用
DHT11是一个温湿度传感器
因为比赛提供dht11的驱动文件,所以直接把.c和.h文件添加到我们的HARDWARE文件夹下,并且添加进工程。
PA7通过跳线帽与P3上对应引脚相连就可以驱动dht11温湿度传感器了
同样,我猜测还是不会提供读取的代码,
所以说,还是得切记这个代码
unsigned int dht11_read(void)
{
int i;
long long val;
int timeout;
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
delay_us(18000); //pulldown for 18ms
GPIO_SetBits(GPIOA, GPIO_Pin_7);
delay_us( 20 ); //pullup for 30us
mode_input();
//等待DHT11拉高,80us
timeout = 5000;
while( (! GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_7)) && (timeout > 0) ) timeout--; //wait HIGH
//等待DHT11拉低,80us
timeout = 5000;
while( GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_7) && (timeout > 0) ) timeout-- ; //wait LOW
#define CHECK_TIME 28
for(i=0;i<40;i++)
{
timeout = 5000;
while( (! GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_7)) && (timeout > 0) ) timeout--; //wait HIGH
delay_us(CHECK_TIME);
if ( GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_7) )
{
val=(val<<1)+1;
} else {
val<<=1;
}
timeout = 5000;
while( GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_7) && (timeout > 0) ) timeout-- ; //wait LOW
}
mode_output();
GPIO_SetBits(GPIOA, GPIO_Pin_7);
if (((val>>32)+(val>>24)+(val>>16)+(val>>8) -val ) & 0xff ) return 0;
else return val>>8;
}
/***********************************************
*****************DHT11***************************
************************************************/
//读出来的值一定要这么写!!!不然没值
dht11_value=dht11_read();
Delay_Ms(10);
if(dht11_value!=0)
{
sprintf(temp,"%s%2d%%"," shidu:",dht11_value>>24);//湿度值=dht11_value>>24
LCD_DisplayStringLine(Line4 ,temp);
sprintf(temp,"%s%2d"," wendu:",(dht11_value>>8)&0XFF);//温度值=(dht11_value>>8)&0XFF
LCD_DisplayStringLine(Line6 ,temp);
}
3.13 三轴传感器
这个东西一般不会考察,就像蜂鸣器一样,考察蜂鸣器考场会乱成一锅粥,这个东西也要拿着板子转来转去,而且它占用引脚也多,但是保不齐这几年把扩展板玩的没什么可考察的了,就出个三轴传感器的题目,最好还是要掌握一下驱动怎么写。
- PA4~7都不能作为其他用处,三周传感器需要使用到这四个引脚资源
- P2全部短接
这个外设的通信协议也是I2C跟我们之前说到的E2PROM一样,所以我们就轻车熟路了。
主要是以下两点: - 更改I2C驱动里的SDA和SCL引脚
- 正确配置三轴传感器
/** 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函数里面的使用
int main()
{
u8 *p;
u8 str[20];
i2c_init();
if(LIS302DL_Check() == 1)//判断三轴传感器是否挂载
{
LCD_DisplayStringLine(Line1, (u8 *)" OK ");
}
else
{
LCD_DisplayStringLine(Line1, (u8 *)"ERROR");
}
LIS302DL_Config();//初始化三轴传感器
while(1)
{
p=Lis302DL_Output();//读取三轴传感器的值
LCD_ClearLine(Line2);//清除第二行显示的内容
sprintf(str," X= %d ",(int)p[0]);
LCD_DisplayStringLine(Line2, str);
LCD_ClearLine(Line3);
sprintf(str," Y= %d ",(int)p[1]);
LCD_DisplayStringLine(Line3, str);
LCD_ClearLine(Line4);
sprintf(str," Z= %d ",(int)p[2]);
LCD_DisplayStringLine(Line4, str);
}
}
3.14光敏电阻的使用
一共有两个光敏电阻,一个直接读引脚高低判断光强,一个是根据输入电压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);
}
PA4的初始化为ADC就行,我就不写了。
4、客观题
4.1 片上资源
片内资源是可以通过数据手册查找到的,具体可以打开考场提供数据手册"stm32f103rbt6.pdf"查看:
- 128K闪存(flash)
- 20K SRAM
- 3个通用定时器(TIM2、TIM3、TIM4)
- 一个高级定时器(TIM1)
- 2个SPI (SP1、SPI2)
- 2个I2C (I2C1、I2C2)
- 3个USART (USART1、USART2、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
4.2 可能会考到的stm32知识
可能考到的基本都在数据手册能找到,比赛时应该是提供英文版手册,我建议在比赛前把手册都看一遍,至少能知道对应知识点在哪里找。
4.2.1 嵌套的向量式中断控制器(NVIC)
STM32F103xx增强型产品内置嵌套的向量式中断控制器,能够处理多达43个可屏蔽中断通道(不包括 16个Cortex™-M3的中断线)和16个优先级。
- 紧耦合的NVIC能够达到低延迟的中断响应处理
- 中断向量入口地址直接进入内核
- 紧耦合的NVIC接口
- 允许中断的早期处理
- 处理晚到的较高优先级中断
- 支持中断尾部链接功能
- 自动保存处理器状态
- 中断返回时自动恢复,无需额外指令开销
记住16个优先级,5个优先级分组
4.2.2 外部中断/事件控制器(EXTI)
外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每个中断线都可以独立地配置 它的触发事件(上升沿或下降沿或双边沿),并能够单独地被屏蔽;有一个挂起寄存器维持所有中断请 求的状态。EXTI可以检测到脉冲宽度小于内部APB2的时钟周期。多达80个通用I/O口连接到16个外 部中断线。
4.2.3 时钟和启动
统时钟的选择是在启动时进行,复位时内部8MHz的RC振荡器被选为默认的CPU时钟,随后可以选择外部的、具失效监控的4~16MHz时钟;
当检测到外部时钟失效时,它将被隔离,系统将自动地 切换到内部的RC振荡器,如果使能了中断,软件可以接收到相应的中断。
同样,在需要时可以采取 对PLL时钟完全的中断管理(如当一个间接使用的外部振荡器失效时)。
多个预分频器用于配置AHB的频率、高速APB(APB2)和低速APB(APB1)区域。
AHB和高速APB的最高频率是72MHz,低速APB的最高频率为36MHz。TIM最高72Mhz
参考如图的时钟驱动框图:
最重要的是要通过时钟框图找到时钟源和相应设备的关系:
比如40KHz的RC振荡器HSI作为独立看门狗(IWDG)的时钟源:
4.2.4 DMA
DMA基本在客观题内算是必考,因为DMA是嵌入式里一个重要的传输手段,而主观题又不好考察,所以一定会在客观题上考察DMA的相应知识点
灵活的7路通用DMA可以管理存储器到存储器、设备到存储器和存储器到设备的数据传输;DMA控 制器支持环形缓冲区的管理,避免了控制器传输到达缓冲区结尾时所产生的中断。
每个通道都有专门的硬件DMA请求逻辑,同时可以由软件触发每个通道;传输的长度、传输的源地 址和目标地址都可以通过软件单独设置。
DMA可以用于主要的外设:SPI、I 2 C、USART,通用、基本和高级控制定时器TIMx和ADC。
STM32 的 DMA 有以下一些特性:
- 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。
- 在七个请求间的优先权可以通过软件编程设置 (共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。
- 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理
- 每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器间的传输
- 外设和存储器,存储器和外设的传输
- 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设均可作为访问的源和目标。
- 可编程的数据传输数目:最大为 65536
4.2.5 看门狗
4.2.5.1 独立看门狗
独立的看门狗是基于一个12位的递减计数器和一个8位的预分频器,它由一个内部独立的40kHz的RC 振荡器提供时钟;
因为这个RC振荡器独立于主时钟,所以它可运行于停机和待机模式。它可以被当 成看门狗用于在发生问题时复位整个系统,或作为一个自由定时器为应用程序提供超时管理。
通过 选项字节可以配置成是软件或硬件启动看门狗。在调试模式下,计数器可以被冻结。
4.2.5.2 窗口看门狗
**窗口看门狗内有一个7位的递减计数器,**并可以设置成自由运行。它可以被当成看门狗用于在发生问题时复位整个系统。
它由主时钟驱动,具有早期预警中断功能;在调试模式下,计数器可以被冻结
4.3 其它方面的知识
客观题没办法速成,还是在于平常的积累,我们只能有针对性的去了解一些方面
- 同步通信和异步通信的区别
- 嵌入式系统的存储体系
- 串行总线和并行总线的区别
- 实时操作系统的知识
- 数组电路的知识
- 模拟电路的知识和计算(主要是二极管、三极管、运算器、放大器)
这些知识掌握的越多越好
5、比赛心得
5.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 / 占空比可调脉冲 / 温湿度传感器 |
5.2 如何备赛
总体路线:
- 首先先把每个独立的驱动搞懂,能够做到顺利编写出来并运用
- 其次再做前几年的模拟题、省赛题、国赛题上
- 最后给自己出题,出自己认为可能考的部分,但是前几年没考的
5.2.1 快速编写驱动
如果你看了我上面对每个驱动代码的编写,你就能知道其实大部分的代码我们都不需要自己敲。
比赛提供的库函数的Example中就有着我们基本需要驱动的代码,我们只需要更改到我们需要的(时钟、引脚等等)即可。
一些函数如果忘记怎么写,对应库函数的头文件也可以找到。
5.2.2 每做一次新的赛题重新开始
希望每次重新做一道主观题时,都能从最基础的液晶驱动参考例程上写逻辑层上的代码,切记不要复制粘贴。
因为比赛时和平常做东西不一样,即便你认为自己原理都懂、驱动都知道怎么写,如果没有做到非常熟练,难免会出现小失误,而小失误有可能会耽误大量的时间,所以一定要细心。
所以既然选择了参加比赛,那么我们就期望准备约充分越好,对基础驱动编写越快越好,对各个模块越熟悉越好
5.2.3 对备赛时间的分配要有规划
如果你是小白,我建议你先去学学别的stm32的教程,因为这个比赛的参考资料太少了,但是在写32的很多时候需要知道原理,然后在开始联系这个比赛。
如果你有一定的stm32或是其他嵌入式设备的开发基础,那么根据你的学习能力来分配备赛时间
我省赛前一共用了两周时间大概是这么分配的:
- 第一周准备我的小项程序(LED -> Systick(delay_ms()) -> KEY -> LCD -> ADC -> RTC -> USART -> E2PROM(I2C) )
- 第二周开始练习往届题目,做了5-9届的一些模拟题和省赛题
- 刚开始做模拟题肯定会很慢,7、8个小时才能完成任务,训练两三届的题就会越来越顺手了,我后来是每晚写一个任务
5.3 如何比赛
5.3.1 不要提前离场
比赛时候总有一些同学提前离场,也不知道是全部做出信心满满还是做不出来直接放弃
我认为既然已经选择了来比赛,比赛前也做了那么多的准备,就应该尽最大的努力去完成比赛
如果已经做出来了程序的所有任务,觉着比赛稳了:
- 可以再检查检查自己的客观题,毕竟国二以上都是在客观题上分高低
- 还可以检查检查自己的程序,多测试几组数据看看有无bug
- 还可以修改修改代码,简化算法,添加注释什么的
总之,准备几个星期就为了这5个小时,提前走总有些得不偿失
5.3.2 对比赛时间的分配要有规划
省赛每个地方不一样,可能不会让提前进场,我就假设5个小时的时间:
-
拿到题后先看一遍主观题,在心里留下个印象
-
开始做客观题,客观题一般也不会耗费多少时间,20分钟顶天了,在做客观题的时候大脑也在想主观题的框架
-
做完客观题记得提交一遍,开始做主观题写程序
-
如果提前做完当然好,检查检查,优化优化,排查一切可能出现的bug
-
如果临比赛结束还有半小时没写完,先把现有的程序做成能顺利运行不会宕机的程序,提交一遍
-
一定要保证自己在还有半小时结束时客观题和程序都提交了一遍,先保证自己有个分
国赛一般提前半小时可以进赛场,排队进,早排队的大概能比最后的早进十几分钟,所以尽量早去多争取十几分钟: -
提前进场后,一般是发板子的,我当时去的时候板子就在我旁边,于是我们可以利用这20分钟先测试烧写屏幕例程看板子是否正常,然后写个LED、按键、ADC的驱动(LED、按键肯定要考,ADC百分之90考)
-
充分利用进场的时间,如果做的好,应该可以完成LED、按键、ADC的工作
-
拿到赛题后,先看一遍主观题,直接做客观题,做完一定记得提交
-
一定要看好如何提交程序,千万不要错,不然自己得不偿失
-
国赛的后半小时会乱糟糟,很多人提前离场,所以还是在离结束半小时时,调通程序,一定要提交一遍所有客观题和程序
再次说明一下离结束半小时提交的重要性:
- 后半小时是提交高峰,赛点服务器拥堵,如果放到最后几分钟提交可能会有提交不成功现象发生,所以我们后来需要改一遍就提交一遍,避免意外发生
- 后半小时很多人提前交卷离场,来来回回进进出出,万一哪个麻瓜碰了一下你的电源线,直接GG
- 后半小时理论上不会有什么太大的改变,但是你提交过一次后,最起码心里有底,不会很慌,说不定还会有突破,解决了没解决的问题
5.3.3 碰到问题千万不要放弃
所有比赛基本上都会碰到问题:ACM、电赛、我们参加的蓝桥杯都一样,都会碰到或大或小的问题
要明确一点:
- 如果一点难度都没有就拿奖证明你的水平已经高于这个比赛了,那么你拿奖是理所应当,并没有值得高兴的
- 碰到问题,遇到困难,想办法解决了,获得最后成功才是最快乐的
碰到问题一定要用自己掌握的知识和训练的经验去一步步解决,冷静分析,不要放弃
6、闲谈
加油加油,第十届国赛国特等奖是台华为的笔记本电脑,这个奖励可不差哦,尤其是还能获得免费的北京4日游,刺激啊,但(国一是搜狗录音笔、、、、、//喷血)。
最后,在附上一些客观题
1、STM32F103RBT6单片机具有(20)KByteRAM空间,(128)Kbyte Flash空间,(3)个USART,(2)个12位ADC。
解析:看STM32_MD系列的数据手册第一页即可,记不住也不用死记硬背,现用现查。
2、STM32 F 103 C 8 T 6 A中各个参数的含义
F = 通用类型
103 = 增强型
101 = 基本型
102 = USB基本型,USB 2.0全速设备
105或107 = 互联型
T = 36脚
C = 48脚
R = 64脚
V = 100脚
Z = 144脚
4 = 16K 闪存存储器
6 = 32K
8 = 64K
B = 128K
C = 256K
D = 384K
E = 512K
H = BGA
T = LQFP
U = VFQFPN
Y = WLCSP64
6 = -40°C ~ 85°C
7 = -40°C ~ 105°C
A或者 空(内部代码,详见产品数据手册)
3、以下哪种方法或工具可以对STM32进行程序下载( ABCD )
A. J-link
B. Co-Link
C.USART ISP
D.USART IAP
解析:J-link是最常用的这里就不多言了,Co-link即使以前不知道,现在准备了蓝桥的嵌入式比赛应该也就知道了。USART-ISP呢,想想正点原子的Fly-MCU,大概就是那个东西,下载hex的软件。USART-IAP是用来更新程序的,可参考这里。
4、下面哪些描述是STM32 GPIO具备的特点( ABCD )
A. 单独的位设置、清除
B. 外部中断线/唤醒线
C. 复用功能和重映射
D. GPIO锁定机制
解析:…
5、模拟信号采集设备,ADC参考电压为5V,要求分辨率达到5mV,ADC至少应选择( B )
A. 8位
B. 10位
C. 12位
D. 16位
解析:我咋记得当时蓝桥杯单片机比赛的时候也有这个题呢?
5V / 5mV = 1000,大概分成1000份。而2^10 = 1024,也是1000多份…
6、STM32 DMA控制器可编程的数据传输数目最大为( D )
A. 65536
B. 4096
C. 1024
D. 65535
解析:该系列单片机的1个DMA,包括7个通道。每个通道都直接连接专用的硬件 DMA 请求,每个通道也都支持软件触发,并且也可以设置优先权。每个通道都有 3 个事件标志(DMA 半传输、DAM 传输完成、DMA传输出错)可以请求中断。通道传输的数据量是可编程的,最大 65535 个字节。
7、某系统需要永久存放少量(少于1K byte)参数,且需要频繁访问,最合适的存储器是( B )
A. SRAM
B. E2PROM
C. Nor Flash
D. Nand Flash
解析:少量数据,当然放EEPROM存取比较好了。
8、运算放大器的电源接入±12V,稳压管的稳定电压为6V,正向导通电压为0.6V,当输入电压Ui = -2V时,输出电压UO应该为( C )
A. -6V B. -2V
C. +6V D. 0.6V
解析:负相端-2V,正相端0V,所以输出为+12V,又由于稳压管作用在输出,所以输出+6V
9、以下哪几种操作系统适合在STM32系列微控制器( AB )
A. μCos-II
B. Free RTOS
C. Windows CE
D. Linux
解析:…
10、STM32内部提供了电压调节器,复位后电压调节器总是使能的。根据应用方式它以如下3 种不同的模式工作( ABC )。(多选)
A.运转模式
B.停止模式
C.待机模式
D.低功耗模式
解析:
运行模式:调节器以正常功耗模式提供1.8V电源(内核,内存和外设);
停止模式:调节器以低功耗模式提供1.8V电源,以保存寄存器和SRAM的内容;
待机模式:调节器停止供电。除了备用电路和备份域外,寄存器和SRAM的内容全部丢失。<参考>
11、不是STM32的低功耗模式有( D )。
A.睡眠模式
B.停止模式
C.待机模式
D.运转模式
解析:三种低功耗:睡眠、停止、待机
睡眠模式:Cortex-M3内核停止,所有外设包括Cortex-M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行;
停止模式:所有时钟都已停止。
待机模式:1.8V内核电源关闭。<参考>
12、在停止模式下,如下哪些是正确的( ABC )。(多选)
A.1.8V供电区域的所有时钟都被停止
B.PLL、HIS 和HSE RC振荡器的功能将被禁止
C.SRAM和寄存器内容将被保留下来
D.SRAM和寄存器内容将被丢失
解析:停止还是会保存SRM的内容的。
13、每个DMA通道具有( A )个事件标志。
A.3
B.4
C.5
D.6
解析:每个通道都有 3 个事件标志:DMA 半传输、DAM 传输完成、DMA传输出错。
14、DMA控制器中,独立的源和目标数据区的传输宽度为( ABC )(多选)。
A.字节
B.半字
C.全字
D.以上都不可以
解析:看下资料查证下,确实是字节、半字和全字都可以。
15、STM32中,1 个DMA请求占用至少( B )个周期的CPU 访问系统总线时间。
A.1
B.2
C.3
D.4
解析:记住
16、在STM32中,备份寄存器是( A )的寄存器。
A.16 位
B.32 位
C.8 位
D.4 位
解析:STM32都有备份寄存器,但是备份寄存器的数量却不一定相同!对于大容量(HD)的微处理器系列来说,它有着42个16位的备份寄存器,而中小容量的微处理器却只有10个16位的备份寄存器。总体来说备份寄存器都是16位的。
17、为了允许访问备份寄存器和RTC,电源控制寄存器(PWR_CR)的DBP 位必须置为( A )。
A.1
B.2
C.0
D.3
解析:使能(置1)后备区域时钟后还要使能电源的寄存器 PWR_CR 的 DBP 位来取消后备区域的写保护。
18、下列哪个不是备份寄存器( C )。
A.BKP_DR1
B.BKP_DR3
C.BKP_RTCCR
D.BKP_DR5
解析:记住
19、若看门狗WWDG被启动,当递减计数器的值小于( A ),则产生复位。
A.0x40
B.0x70
C.0x4F
D.0x7F
解析:看资料可查证到
20、在寄存器IWDG_KR中写入( A ),开始启用独立看门狗。
A.0xCCCC
B.0xBBBB
C.0xAAAA
D.0xDDDD
解析:查阅相关资料
21、如果窗口看门狗启动,并且当7 位(T[6:0])递减计数器 ( A )时,则产生一个复位动作。
A.从0x40翻转到0x3F
B.从0x50翻转到0x4F
C.从0x60翻转到0x5F
D.从0x70翻转到0x6F
解析:看19题的图
22、STM32的可编程TIM1定时器的时基单元包含( ABCD )。(多选)
A.计数器寄存器(TIM1_CNT)
B.预分频器寄存器 (TIM1_PSC)
C.自动装载寄存器 (TIM1_ARR)
D.周期计数寄存器 (TIM1_RCR)
解析:TIM的基础…
23、高级定时器TIM1的特性( ABCD )。(多选)
A.具备16位上,下,上/下自动装载计数器
B.具备16位可编程预分频器。
C.可以在指定数目的计数器周期之后更新定时器寄存器。
D.可以通过事件产生中断,中断类型丰富,具备DMA功能
解析:高级定时器的特征
24、定时器TIM1的特殊工作模式包括( ABCD )。(多选)
A.输入捕获模式
B.PWM 输入模式
C.编码器接口模式
D.单脉冲模式(OPM)
解析:定时器的4种工作模式
25、STM32的可编程通用定时器的时基单元包含( ABC )。(多选)
A.计数器寄存器(TIMx_CNT)
B.预分频器寄存器(TIMx_PSC)
C.自动装载寄存器(TIMx_ARR)
D.以上都不是
解析:同22题
26、ARM Cortex-M3不可以通过( D )唤醒CPU。
A.I/O端口
B.RTC 闹钟
C.USB唤醒事件
D.PLL
解析:记住…
27、STM32嵌套向量中断控制器(NVIC) 具有( A ) 个可编程的优先等级。
A.16
B.43
C.72
D.36
解析:之前的一点记录<链接>
28、STM32的外部中断/事件控制器(EXTI)支持( C )个中断/事件请求。
A.16
B.43
C.19
D.36
解析:参考正点原子《STM32F1开发指南-库函数版本_V3.1 》
STM32F103 的中断控制器支持 19 个外部中断/
事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103 的
19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
注意,供IO口使用的中断线是16个。
29、哪些是STM32的ADC系统的特点( ABCD )。 (多选)
A.12-位分辨率
B.自校准
C.可编程数据对齐
D.单次和连续转换模式
解析:ADC的基础知识
30、在ADC的扫描模式中,如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到( A )中。
A.SRAM
B.Flash
C.ADC_JDRx寄存器
D.ADC_CR1
解析:记住…
31、STM32规则组由多达( A )个转换组成。
A.16
B.18
C.4
D.20
解析:规则组最多可以使用 16 个通道,注入组最多可以使用 4 个通道。
32、在STM32中,( A )寄存器的ALIGN位选择转换后数据储存的对齐方式。
A.ADC_CR2
B.ADC_JDRx
C.ADC_CR1
D.ADC_JSQR
解析:ALIGN位在寄存器ADC_CR2中
33、STM32的Flash闪存编程一次可以写入( A )位。
A.16
B.8
C.32
D.4
解析:STM32内部FLASH操作一次固定16位,记住。<链接>
34、STM32主存储块的页大小为( A ) 字节。
A.1K
B.3K
C.2K
D.4K
解析:STM32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。
小容量产品主存储块1-32KB, 每页1KB。系统存储器2KB。
中容量产品主存储块64-128KB, 每页1KB。系统存储器2KB。
大容量产品主存储块256KB以上, 每页2KB。系统存储器2KB。
互联型产品主存储块256KB以上, 每页2KB。系统存储器18KB。
35、用户选择字节的大小为( A )。
A.512字节
B.2K
C.1K
D.128K
解析:记住
36、下列哪些不是STM32闪存存储器的特点( C )。
A.大容量
B.高速
C.掉电不保存
D.具有选择字节加载器
解析:FLSH的内容
37、在APB2上的I/O脚的翻转速度为( A )。
A.18MHz
B.50MHz
C.36MHz
D.72MHz
解析:记住吧<链接>
38、定时器2的TIM2_REMAP[1:0]为“10”和“11”的重映射,适用于( ABD )封装的芯片。
A.64引脚
B.100引脚
C.36引脚
D.144引脚
解析:手册上明确将,重映像不适用与36脚的封装,参考《STM32中文参考手册_V10》P119
39、USART2的USART2_REMAP = 1的重映射只适用于( BD )引脚的封装。
A.64引脚
B.100引脚
C.36引脚
D.144引脚
解析:按照参考手册
40、当输出模式位MODE[1:0]=“10”时,最大输出速度为( B )。
A.10MHz
B.2MHz
C.50MHz
D.72MHz
解析:资料中可查阅到MODE对应两位的输出速率,在此可以看到是2M
41、下列哪个不是RealView MDK开发环境的特点( D )。
A.Windows风格
B.兼容的Keil μVision界面
C.全面的ARM处理器支持
D.体积庞大
解析:…
42、下列哪种方法可以对STM32进行程序下载( ABC )。(多选)
A.Keil ULink
B.J-Link
C.在应用编程
D.以上都不可以
解析:…
43、STM32的USART根据( A )寄存器M位的状态,来选择发送8位或者9位的数据字。
A.USART_CR1
B.USART_CR2
C.USART_BRR
D.USART_CR3
解析:记住设置数据字节位数看:CR1寄存器,设置停止位个数由CR2确定…
44、STM32的bxCAN的主要工作模式为( ABCD )。
A.初始化模式
B.正常模式
C.环回模式
D.睡眠模式
解析:记住…
45、在程序中,可以将CAN_BTR寄存器的( AB )位同时置1,来进入环回静默模式。(多选)
A.LBKM
B.SILM
C.BTR
D.以上都不
解析:记住吧
46、STM32提供了三种不同的时钟源,其都可被用来驱动系统时钟SYSCLK,这三种时钟源分别为( ABC )。
A.HSI振荡器时钟
B.HSE振荡器时钟
C.PLL时钟
D.HLI振荡时钟
解析:…
47、在STM32中,当( AB )发生时,将产生电源复位。(多选)
A.从待机模式中返回
B.上电/掉电复位(POR/PDR复位)
C.NRST管脚上的低电平
D.PLL
解析:,注意是电源复位,新知识…
48、以下哪个时钟信号可被选作MCO 时钟( ABCD )。(多选)
A.SYSCLK
B.HSI
C.HSE
D.以2分频的PLL 时钟
解析:MCO时钟,emmm,又一个新的知识点
对应上图A为MCO时钟…
参考文献:
https://blog.csdn.net/Zach_z/article/details/80548423
https://blog.csdn.net/ReCclay/article/details/86699835