本文是我参加蓝桥杯嵌入式比赛后的一些经验和我自己总结的一些驱动代码,希望能给未来参加蓝桥杯嵌入式比赛的学生带来一些帮助。
本文未校对。如有错误,请包容。欢迎交流指正。请注明转载来源。
蓝桥杯赛点数据包
一、 总述
自身情况:
我参加了第十届蓝桥杯嵌入式比赛
省赛备赛两周(平均每天4-6小时),最后省一进入国赛
国赛备赛一周半(每天晚上写一个省赛或者国赛项目),最后一个国赛
我以前学过stm32可以快速上手(看到这里不用担心,拿不拿奖,有没有?stm32使用经验关系不大。
谈谈对蓝桥杯嵌入式比赛的理解: 简介套话不说,这个比赛我个人认为得奖很容易,但想拿国一及以上就有些难度了,需要一定的训练加一定的运气。比如当时需要自己写188。b我刚背了20个读取函数,然后幸运地写了出来。官员只给底层驱动,比如他给你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 串口调试工具
进入考场后,首先要检查板载情况FT插入2232后是否可用,是否可以下载程序,是否可以使用串口,特别是是是否可以下载 程序
2.1.2.3 数据手册资料图:
这些数据手册各有用处,我认为最有用stm32f103rbt6.pdf,如何看数据手册,什么时候看后面会讲。其他常用的需要背下来,比如24c当时我用了所有的省赛国赛
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()函数上,这样我们就省去了定义这个步骤。它能使我们的发展更快。
3.1LED驱动
/*********************************************** *****************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 |
/*************************************************************
***************************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串口驱动
初始化如果有问题先看有没有打开端口,串口有时候如果不行就把初始化位置移一下,
把接收引脚改成PA3,发送引脚改成PA2即可,中断IRQChannel改成USART2_IRQn,再使能对应的时钟即可。 然后再打开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完成时钟
直接复制粘贴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,方便下次使用。
- 取用基础时钟设置的代码
- 取用中断配置的代码
- 中断设置改为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采样的软件驱动(也可用于一路)
因为我不需要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
3.8.1捕获两路路PWM的频率(我写的是两路的,一路更简单,)
变量位置
/*********************************************************************
************************输入捕获*****************************************
******************************************************************/
参考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
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输出频率固定改变占空比
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的频率和占空比
只需要更改到你所需的引脚和对应的时钟通道即可
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的驱动文件,所以直接把.c和.h文件添加到我们的HARDWARE文件夹下,并且添加进工程。 这里需要注意,比赛不给读取函数,需要自己写,就是我下图圈的部分没有,这个没有啥好办法,背下来解决一切问题。我当时就是运气好,刚好记得大致时序,不然我就把自己玩死了。 这里我是不建议你花时间去看数据手册的,太花时间了。 这里最重要的就是数据拼接,一定要切记读出来的开始八位是高位。然后读出来的值。
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知识
可能考到的基本都在数据手册能找到,比赛时应该