微信搜索:ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载优快云资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。
这里再向各位同学推荐一个优快云博主 ReRrain 的蓝桥备赛博客,博主秉持初学者思路,向你讲述自己蓝桥备赛的心路历程,娓娓道来蓝桥备赛经验,个人觉得非常不错,值得细细品读。
文章目录
导读:《蓝桥杯嵌入式组》专栏文章是博主2019年参加蓝桥杯的嵌入式组比赛所做的学习笔记,在当年的比赛中,由于忙于准备考研及保研相关工作,博主仅仅参加了当年的省赛,并获得了省赛一等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。
“一叶遮目,不见泰山”。不论何事,只有把握事情的总体趋势,才能做到心中有数。
底层代码参考建Gihub<传送门>
模块 | 模式 |
---|---|
LED | 普通推挽输出(GPIO_Mode_Out_PP) |
KEY | 浮空输入(GPIO_Mode_IN_FLOATING) |
BEEP | 普通推挽输出(GPIO_Mode_Out_PP) |
ADC | 模拟输入(GPIO_Mode_AIN) |
USART2_RX | 浮空输入(GPIO_Mode_IN_FLOATING) |
USART2_TX | 复用推挽输出(GPIO_Mode_AF_PP) |
PWM输出 | 复用推挽输出(GPIO_Mode_AF_PP) |
PWM捕获 | 浮空输入(GPIO_Mode_IN_FLOATING) |
1、移植LCD前先看看,能不能直接下载到开发板,如果可以,直接用,不必再去费事移植。
2、工程可以使用后,打开数据手册和原理图。【备查引脚使用】
3、写定时器4底层
- 参考固件库中的
\Project\STM32F10x_StdPeriph_Examples\TIM\TimeBase
- 配置好相应的NVIC和TIM,注意前提打开时钟
- 最后开启定时器并且开启定时器更新中断【不会写的变量名直接索引到底层查看】
- 写中断函数【不会的函数名,可在启动文件中索引】,以及上述例程的中段文件
- 注意中断函数名字是TIM4_IRQHandler,不是TIM4_Handler
- 最后在主函数中调用
TIM4_Init(2000, 72);//72分频,计数值2000 -- 2ms定时
即可。【注意参数位置和具体大小】 - 复制的时候竟然忘了改定时器名字,造成分频和周期值没有装进去,定时特别快!!
4、写LED底层
- 依旧可以参考上述定时器的那个例程
- 配置好时钟和GPIO【注意PD2是锁存器选通引脚,PC8~PC15是LED对应管脚,全都配置成强推挽输出,最后初始化定时器初始状态为关闭】
GPIOD->ODR |= (1<<2);
GPIOC->ODR = 0xFFFF;//小灯初始状态关闭
GPIOD->ODR &=~(1<<2);
- 在主函数中用Delay_Ms测试闪烁
- 无误后用LED测试定时器4
5、写KEY底层
- GPIO配置可复制LED底层
- 配置好四个引脚PA0、PA8、PB1、PB2【注意的是这几个脚都是浮空输入】
- 注意别忘了【打开时钟】
- STM32的按键配置需要初始化GPIO,并且在主函数中调用也别忘了。
- 书写的时候直接也一并把长按键也写上【长按键的KeyDown初始化为0】
- 别忘了在.h总直接宏定义KEY1~KEY4,用
GPIO_ReadInputDataBit函数
- 最后需要在主函数初始化KEY,并在while1中调用KeyDriver,并在中断中按键扫描
- 易错的地方:忘记重新更新backup
6、写BEEP底层
- 跟LED大部分一样,可直接参考LED部分
- 需要主要的是PB4是JTAG的JTRST引脚,作为普通IO的话需要将其禁用,禁用方法就时调用
GPIO_PinRemapConfig
,对了别忘了开启AFIO时钟! - 务必先开启
AFIO
时钟,然后再GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST, ENABLE);
- 然后再编写独有的BeepScan(…)和Beep(…)函数
- 全局变量BeepTimer是个有符号32变量
- 最后可通过按键测试
7、写EEPROM底层
- 不需要再写I2C底层,可直接从对应的资料中
嵌入式设计与开发\I2C参考程序
找到i2c对应底层,并复制到相应位置。 - 写的过程“启动->写入写操作指令0xA0->等待回应->写入存储地址->等待回应->写入数据->等待回应->停止”
- 读的过程“启动->写入写操作指令0xA0->等待回应->写入存储地址->等待回应 // 重新启动->写入读操作指令0xA1->等待回应->读取数据->等待回应->停止”
- 需要在主函数中初始化
i2c_init()
- 使用液晶显示E2中的数据,需要用到sprintf,加入
stdio.h
头文件
8、写ADC底层
- 可参考之前写的相关配置以及
ADC\ADC1_DMA
例程配置 - 需要使能GPIOB(配置为模拟输入模式)和ADC1时钟
- ADC1时钟需要进行6分频
- 同时需要把
ADC_ScanConvMode
和ADC_ContinuousConvMode
改为DISABLE,表示单通道和单次转换 - 最后使能ADC1
- 启动ADC校准(四行代码)
- 读取ADC时候,配置
ADC_RegularChannelConfig
为通道8,且为239.5转换周期->启动软件触发->等待EOC标志位(可到adc底层文件看相关变量)->获取ADC转换值->关闭软件触发 - 注意等待ECO的标志位是:
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == 0);
日哦 - 注意ADC初始化命名的时候不能那个叫
ADC_Init(void)
,底层函数有一个函数是叫这个了,这里起名为void ADC1_Init()
- 等待转换完成是FLAG标志位不是IT标志位
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == 0);
9、写RTC底层
- 可参考固件库中的
\RTC\Calendar
例程 - 需要修好的是LSI时钟部分,使能LSI时钟
RCC_LSICmd(ENABLE);
,接下来是等待使能完毕 - 最后要初始化
RTC_SetCounter
,然后RTC_WaitForLastTask
- 秒转换为对应的单位时,关系如下
HH = time / 3600;
MM = time % 3600 / 60;
SS = time % 3600 % 60;
24:00:00
特殊处理下
if(time == (23*3600 + 59*60 + 59))
{
RTC_SetCounter(0);
}
HH = time / 3600;
MM = time % 3600 / 60;
SS = time % 3600 % 60;
sprintf((char*)str, "RTC: %.2d:%.2d:%.2d ", HH, MM, SS);
LCD_DisplayStringLine(Line6, str);
10、写USART2底层
- 参考
USART\DMA_Interrupt
例程 - 别忘了先开启对应的GPIO和USART的时钟
- 接下来配置TX为复用推挽输出,RX为浮空输入,然后配置中断向量
- 接着配置USART对应的结构体成员
- 最后启动USART,并开启串口接收中断
- 串口发送中断采用查询的方式,所以接收对应标志位是IT相关,而发送对应的标志位是FLAG相关的,要特别注意!
- 在接收中断中,一旦判断到结束条件就要关闭接收中断;在主函数中进行数据处理完毕后再打开接收中断,记得清零对应的接收数组
- 发送数据的时候不需要打开中断,而是采用查询的方式
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == 0);
- 还需要注意TXD配置为复用推挽输出
for(i=0; i<20; i++)
{
RxdBuf[i] = 0;
}
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
11、写PWM模式底层
- 参考
\TIM\7PWM_Output
例程 - 使能GPIO时钟和TIM时钟
- 注意GPIO此时应该是强推挽输出
- 形参传入
freq
和duty
- 由
freq
算周期Period
的方法为:1000000/freq
,分频系数为72
- 占空比由
Pulse
控制:(arr-1) * duty / 100;
- 看清楚是哪个定时器输出,配置好定时器的周期分频值。
- 看清楚是定时器的第几个通道,相应的
TIM_OCxInit
中的x就是几 - TIM_OCMode和TIM_OCPolarity分别对应关系是:TIM_OCMode_PWM2-TIM_OCPolarity_Low,TIM_OCMode_PWM1-TIM_OCPolarity_High
12、写输出比较产生PWM底层
- 参考PWM模式的底层
- 加入NVIC配置,以及捕获比较中断
- TIM_Period为:0xFFFF
- TIM_Pulse为:CH2_Val
- TIM_OCMode为:TIM_OCMode_Toggle也可以是PWM1
- 初始化计数器值和比较值
TIM_SetCounter(TIM2, 0);//计数器清0
TIM_SetCompare2(TIM2, 0);//设置比较值
- 开启捕获比较中断
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE)
,注意对应的定时器以及通道 - 中断里面
- 先获取当前的捕获值:
capture = TIM_GetCapture2(TIM2);
- 然后通过设置一个静态表示为分别设置相应的高电平和低电平比较值,别忘了变化标志位的值
- 注意中断里面获取当前的比较值(捕获值)用
TIM_GetCapture2
,当然也可以用TIM_GetCounter
但是计数器是不断在走的,可能会有些许的不准确 - 别忘了初始化的时候清零计数器的值以及比较值
- 先获取当前的捕获值:
if(flag)
{
TIM_SetCompare2(TIM2, capture+CH2_Duty);
}
else
{
TIM_SetCompare2(TIM2, capture+(CH2_Val-CH2_Duty));
}
flag ^= 1;
- 别忘了开启对应的时钟
- 别因为复制,写错了定时器名字,是定时器2还是3
- 还要注意是通道几输出…
- 计算占空比对应的计数器值时,别忘了是
TIM2_CH2_VAL * duty / 100
中的除以100!!!
13、输入捕获
- 参考例程
TIM\InputCapture
… - 基本上仍类似于输出比较产生PWM的底层,但是要注意的是,引脚模式需要设置为浮空输入
- TIM_Period仍然为0xFFFF;
- 开启捕获比较中断
TIM_ITConfig(TIM3, TIM_IT_CC2, ENABLE);
- TIM3_CH2_CAPTURE_MODE表示当前捕获所在位置
- mode=0的时候表示刚开始,此时需要更新mode;初始化H和HL,设置计数器;改变触发方式为下降沿触发(初始化为上升沿触发)函数为
TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling);
- mode=1的时候:更新mode;获取H的值
TIM_GetCounter(TIM3);
;改变触发方式为上升沿触发。 - mode=2的时候,更新mode的值,获取HL的值
- 最后在主函数中处理mode=3的情况,而后算出对应的占空比(第一次计数值/第二次计数值)(要想正确显示需要扩大100倍)和频率(1000000/第二次计数值),显示出来。最后别忘了吧mode模式重新置为0
- 傻了,,,,当然还需要配置定时器啊,不然怎么计数!!!
- 第一步想,肯定要从头开始,也就意味着清除第一次、第二次捕获值和计数器的初值,然后改变捕获方向
- 第二步肯定还需要,改变捕获方向,下降沿捕获
- 竟然忘了写NCIV向量
- IO模式是浮空输入,开启输入捕获2是
TIM_ITConfig
14、高级定时器的互补PWM输出
- 分别对应CH×和CH×_N
- 基本和普通的PWM模式配置一样,只是需要加上互补引脚的一些配置
- 互补引脚的GPIO(当然包括对应的时候啦)、
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
、以及TIM_CtrlPWMOutputs(TIM1, ENABLE);
这个三个配置是必须要的,不然根本不会输出PWM - 其他附加的配置,可以
TIM_OCNPolarity
和TIM_OCPolarity
一样、TIM_OCIdleState
为Set、TIM_OCNIdleState
为Reset,但是似乎不配置这三个也可以的!
- 互补引脚的GPIO(当然包括对应的时候啦)、
结语:以上就是本篇文章的全部内容啦,希望大家可以多多支持我的原创文章。如有错误,请及时指正,非常感谢。
微信搜索:ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载优快云资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。