电子信息工程专业打工人的蓝桥杯单片机竞赛时记

前言

根据大一单片机自学(蓝桥杯单片机)的学习总结,本文主要针对蓝桥杯单片机作出总结(IAP15F2K61S2型号)进行讲解。如果读者时间充足,建议先学好模电数电基础知识,同时用开发板实际操作,能让学习事半功倍且能学习更深入。


一、基础入门

1.基础知识

单片机全称单片微型计算机,可以看成一个集成了一个微型计算机的芯片,(外设)I/O设备可通过I/O口外接扩展输入输出,常见的单片机型号有STC的51系列和STM的32系列,蓝桥杯单片机属于STC的51系列。
—>单片机百度百科
—>单片机与CPU区别
—>单片机和嵌入式的简单区别
—>各类单片机对比

2.51系列单片机

—>51单片机百度百科

单片机周期知识:

时钟周期/振荡周期:时钟周期又叫做振荡周期、节拍周期,定义为时钟晶振频率的倒数。时钟周期是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。例如晶振为12M,则时钟周期为1/12us。又可以被定义为节拍。

指令周期:指令周期是指取出并完成一条指令所需的时间,一般由若干个机器周期组成,分为单周期指令,双周期指令和多周期指令。对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。

机器周期单位指令的时间(如自增、自减 等)在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成。比如,取值周期,取数周期。在80C51内部,机器周期一般包括于6个状态周期,12个时钟周期。例如24M的晶振,机器周期为12/24M秒。

分频是指将意单一频率信号的频率降低为原来的1/N,周期 = 1 / 频率,12分频 —>1分频,频率升为原频率的12倍,周期越短,速度越快
1T(1分频): 1个时钟周期 = 1个机械周期 1个时钟周期 = 1个振荡周期 = 1个机械周期;
12T(12分频): 1个时钟周期 = 12个机械周期 12个时钟周期 = 12个振荡周期 = 6个状态周期 = 1个机械周期;
1个时钟周期 = 1 / 晶振频率 1个指令周期(执行1条指令/语句) = 若干个机械周期。

15系列(STC15上电默认复位12T模式)与12系列均为1T兼容12T(机械周期),89系列为12T(机械周期),硬件工艺先进性决定单片机执行速度(机器周期),晶振决定振荡频率(时钟周期),振荡脉冲(高低电平总时间)决定状态周期。
—>单片机周期
—>12T与1T单片机区别

相关硬件:晶振—>晶振百度百科

单片机IO口知识

GPIO(general purpose input/output):通用I/O口,芯片引脚引出用于与外接进行信号输入输出,与芯片内部与I/O相连的电路决定I/O口具有的输入输出模式功能。
—>GPIO百度百科
—>GPIO输入输出各种模式(推挽、开漏、准双向端口)详解
上拉下拉电阻:简单理解两个端点的连线上通过一个电阻与电源(VCC)或地(GND)相连接,将该节点的电位拉高或拉低【得到结果还得另外由两个端点的情况】;在一些电路中还有提供电流或拉走电流的作用。
—>上拉电阻与下拉电阻
—>上拉电阻阻值大小
—>高低电平
—>拉电流和灌电流区别
单向口:只能用于输入或用于输出的引脚,只有获取外设电平信号或输出电平控制外设其中一项功能;
单向输入/输出口
双向口/准双向口:既能用于输入又能用于输出的I/O口
—>准双向口和双向口区别
双向口引脚符号

二、开发学习

—>基础学习视频推荐
—>进阶学习视频推荐

1.基本外设功能

—>原理图阅读基础
—>74HC138译码器、74HC02或非门、74HC573锁存器
74HC138译码器:由于开发板外设多且单个外设占用IO口多,单片机IO口不能与外设一一对应,需要译码器作为数据分配器,用P25、P26、P27口作为选择输入口,选择输出给相应锁存器,由于译码器有记忆功能,所以建议有关数据分配器选择的每个不同外设功能实现完成后,将选择输入口清零。
—>数据分配器百度百科
74HC138译码器74HC138译码器功能表

74HC02或非门:蓝桥杯开发板选择IO口模式时工作,WR与GND用跳帽短接,译码器选择单个或非门输入低电平经过逻辑非输出高电平,其他或非门输入高电平经过逻辑非输出低电平;选择存储器扩展(MM)模式由P42口决定或非门是否起作用,若P42口输出高电平,则另一输入端不管输入高低,输出恒为低电平。
74HC02或非门开发板模式选择74HC573锁存器:为了节省单片机I/O口,和74HC138译码器配套使用,每个使用到P0口的外设都需要经过P2口输入给译码器的信号,选择使能锁存器后,P0口对外设的控制生效,可以把锁存器视为单片机的 I/O 口的扩展器,具有缓存作用;并且输出电流比单片机I/O口的输出电流大,相对有电流放大作用,保证对数码管有足够的驱动能力。
—>锁存器百度百科
—>锁存器和触发器的区别
74HC573锁存器
ULN2003:ULN2003是高耐压、大电流复合晶体管阵列,作用是对开发板的继电器或外接电机产生的线圈放电动势进行过滤消除,保护单片机,并且可以输出大电流,保证对外设的驱动能力。
ULN2003百度百科
ULN2003
P2与P0口关系

P2 = P2 & 0x1f | (n << 5)	/*保证其他P2口保持原有高低电平,经过HC02或非门,接通HC573锁存器*/
P2 &= 0x1f;	/*不选择HC573锁存器*/

LED跑马灯

LED(Y4):P2: 0x80	P0: 01灭	单个LED:P0 =  0x01 << (P0 - 1);		P0为形参

蜂鸣器与继电器

蜂鸣器/继电器(L10)(Y5):P2: 0xa0	P0(6\4: 1(L10亮)0关	电路:0通路	蜂鸣器:P0 = P0 & 0xbf | (P0 << 7);	继电器:P0 = P0 & 0xef | (P0 << 5);	P0为形参

硬件基础:发光二极管蜂鸣器继电器
LED蜂鸣器继电器在比赛中的作用常是用于功能提示,只需要在该实现功能时会打开或关闭即可,但注意LED、蜂鸣器、继电器和数码管是通过P2口选择,P0口控制,在P2口选择时,P0口会保存上一个功能使用的赋值,学习和比赛时应考虑到几个外设之间的影响,由于情况太多,所以没有通用的方法进行介绍,但一般通过思考或查找资料都是可以从中找到适合的解决方法。

数码管

共阳数码管数字显示0~9:uchar code gas_smgseg[11] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
初学多个数码管显示常会出现重影亮度低等情况,可能的原因有:
1、单个数码管给的显示时间太短
由于数码管显示不同数字只能通过连续快速单个显示来实现,几位数码管之间时间间隔不均匀会造成功能实现效果不足,过短亮度低、在人眼中重影,过长人眼易察觉、影响功能;
解决方法:给数码管显示间隔做到延时均匀,建议使用定时器中断功能进行延时。
2、使用前没有对P0口做好处理
在前面的LED、蜂鸣器、继电器等外设使用时,P0口保持之前的状态,可能会导致打开数码管P2口时,P0口还未处理,影响功能的实现;
解决方法:每次使用时,先P2使能控制段选的锁存器,将P0口置1(段灭)。
3、使用时代码逻辑功能混乱

数码管(Y6+Y7):com口:P2: 0xc0	P0: 10不选	segment段: P2: 0xc0	P0: 01不亮	单位数码管:P0 =  0x01 << (P0 - 1);	P0为形参	单个数字:P0 = value;(无小数点)/value&0x7f;(有小数点)			value为形参

按键

按键用到的P3口和P4口主要通过IO口的输入读取功能,按键按下时弹片的抖动常会产生误读,信号由于机械的抖动,导致在1和0之间多次跳变,一般抖动时间为5-10ms。单片机的处理速度很快,如果不做处理,那么会导致程序多次执行按键后的操作,从而导致逻辑错误。例如,要实现按下灯亮,再次按下灯灭,如果不消抖,就可能导致一次按下,多次处理,灯的开关状态就有可能没有变化,因此需要进行消抖处理,而常规的延时消抖太过浪费资源且用while判断会阻碍程序的运行,严重时会对程序运行造成很大影响,建议使用独立按键使用三行代码法、矩阵按键使用状态机消抖法;
—>按键消抖方法
延时消抖是通过延时来跳过按键抖动的那段时间和用while一直判断按键是否按下,而三行代码和状态消抖法实际是用软件程序执行来防止程序在按键抖动时,被判断为多次按下和按下时多次执行按键控制的功能;
另:由于单片机I/O口的灌电流远大于拉电流,因此单片机对开关的读取只能通过判断该I/O口

按键:独立按键(J5接23):  S7: P3^0    S6: P3^1    S5: P3^2    S4: P3^3  	按下为0接通
	 矩阵键盘(J5接12):  C1\C2\C3\C4:(row)   / /   R1\R2\R3\R4:(column)  :0选择
	判断行:P3 = P3 & 0xc0 | 0x0f;  P4 = P4 & 0xeb | 0x00;	判断后:key_r = 0x10 / 0x20 / 0x30 / 0x40
	判断列:P3 = P3 & 0xc0 | 0x30;  P4 = P4 & 0xeb | 0x14;	判断后:key_c = 0x01 / 0x02 / 0x03 / 0x04
	判断按键:key_press = key_r | key_c

硬件基础:数码管轻触开关
数码管在比赛中作为数值显示的一个工具,将内部程序的执行结果通过数码管展示,使用时除了一些特殊的符号或特殊数值显示,数码管的数值都是被其他功能控制,学习和比赛时对数码管应做到熟练掌控;轻触按键在比赛中实现功能切换与参数设置的功能,与数码管一样是学习和比赛时必用的一个基础外设。

中断结构
—>单片机中断详解

/*
定义中断服务函数
函数类型 函数名 (形式参数) interrupt n [using n]
*/

interrupt为C51编译器的关键字

外部中断

单片机外部出现了一定的情况,才进入了中断,蓝桥杯单片机的两个外部中断复用在了P32和P33口,打开中断允许控制位后,当使用按键功能时,按下S5触发外部中断0,按下S4触发外部中断1。对比单纯使用按键功能,外部中断可以随时随地通过按下按键执行中断服务函数内的代码,实时响应。

定时器中断

蓝桥杯单片机有3个定时计数器,(STC-ISP烧录软件可直接定时器代码,一般最好用16位自动重装载模式),定时器0和定时器1有四种定时模式,定时器2只有16位重装载模式,12T模式(参考上文单片机周期知识)决定打开计数器后每1/12us加一,定时器功能实际上是通过单片机的计数功能来定时。
所以定时器可以看成一个固定容量(0~65535最大计数值)的水桶,计数看成滴水,TFx(中断请求标志位)置1表示清空水桶,TRx表示打开滴水的水龙头,初始值(THx和TLx)表示桶内原有保持水量(自动重装载模式即为每次清空后自动恢复原有保持水量);数值溢出会自动触发TF1置1,数值清零后TF1自动置0;定时器功能对程序执行产生效果需要打开中断,定时器中断控制位ETx和总中断EA,暂停中断功能只需关闭水龙头TRx,而关闭中断需要下次再重新使用时注意不仅需要关闭水龙头或直接关闭中断还要将定时器清零,并注意该中断是否有其他功能。
定时器中断的好处:使用软件延时相当于人在盯着秒表读秒,这三分钟就没法做其他事情了,使用定时器延时则相当于这个秒表每三分钟就会报一次警(产生中断),只需要在报警时执行事件A;定时器中断可以为程序执行的实时性提供较好的时间保证,但注意若中断服务函数内指令过多会影响定时准确性。
—> 数值溢出详解

串口中断

除默认优先级前五的常用中断,其他中断的中断标志位对用户不可见

分割线************************************************待更新

>>蓝桥杯单片机资料包免费下载

>>蓝桥杯单片机代码程序免费下载

本人通过许多大牛博客自学在大一获得蓝桥杯单片机国赛优秀奖,但自身知识体系沉淀不足,借此博客创作过程继续深入学习扩展知识,借鉴蓝桥杯单片机相关的大牛介绍,让自己的技术更进一步。
—>蓝桥杯单片机博客推荐1
—>蓝桥杯单片机博客推荐2


总结

蓝桥杯单片机语言相关博客网络资料已经较为齐全,以上仅为本人所学的知识总结,本文未涉及未学习到的知识点,其中链接网址仅作记录使用,如有侵权或内容有误,敬请联系斧正。若读者觉得本文对学习有所帮助,不妨Give a like,Respect!!!


  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是基本框架和代码: ```c #include <stdio.h> #include <stdlib.h> #include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "stm32f10x_spi.h" #include "ds1302.h" //DS1302 库头文件 #define GPIO_RCC RCC_APB2Periph_GPIOA //GPIOA 时钟 #define SPI_RCC RCC_APB2Periph_SPI1 //SPI1 时钟 #define CS_Pin GPIO_Pin_4 //片选引脚 #define SCLK_Pin GPIO_Pin_5 //时钟引脚 #define MOSI_Pin GPIO_Pin_7 //数据引脚 void GPIO_Configuration(void); void SPI_Configuration(void); uint8_t SPI_ReadWriteByte(uint8_t byte); void DS1302_WriteByte(uint8_t address, uint8_t dat); uint8_t DS1302_ReadByte(uint8_t address); void DS1302_SetTime(time_t *time); void DS1302_GetTime(time_t *time); int main(void) { time_t time; GPIO_Configuration(); SPI_Configuration(); DS1302_Init(); while(1) { DS1302_GetTime(&time); //读取时间 printf("%d:%d:%d\n", time.hour, time.minute, time.second); //打印时间 Delay(1000); //延时 1 秒 } } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(GPIO_RCC, ENABLE); GPIO_InitStructure.GPIO_Pin = CS_Pin | SCLK_Pin | MOSI_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } void SPI_Configuration(void) { SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(SPI_RCC, ENABLE); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); } uint8_t SPI_ReadWriteByte(uint8_t byte) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){} SPI_I2S_SendData(SPI1, byte); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} return SPI_I2S_ReceiveData(SPI1); } void DS1302_WriteByte(uint8_t address, uint8_t dat) { GPIO_SetBits(GPIOA, CS_Pin); //选中 DS1302 SPI_ReadWriteByte((address << 1) & 0xFE); //发送地址 SPI_ReadWriteByte(dat); //发送数据 GPIO_ResetBits(GPIOA, CS_Pin); //取消选中 DS1302 } uint8_t DS1302_ReadByte(uint8_t address) { uint8_t dat; GPIO_SetBits(GPIOA, CS_Pin); //选中 DS1302 SPI_ReadWriteByte((address << 1) | 0x01); //发送地址 dat = SPI_ReadWriteByte(0xFF); //读取数据 GPIO_ResetBits(GPIOA, CS_Pin); //取消选中 DS1302 return dat; } void DS1302_SetTime(time_t *time) { DS1302_WriteByte(ADDR_WP, 0x80); //进入写保护模式 DS1302_WriteByte(ADDR_SECOND, (time->second % 10) | ((time->second / 10) << 4)); //写秒 DS1302_WriteByte(ADDR_MINUTE, (time->minute % 10) | ((time->minute / 10) << 4)); //写分 DS1302_WriteByte(ADDR_HOUR, (time->hour % 10) | ((time->hour / 10) << 4)); //写时 DS1302_WriteByte(ADDR_DATE, (time->day % 10) | ((time->day / 10) << 4)); //写日 DS1302_WriteByte(ADDR_MONTH, (time->month % 10) | ((time->month / 10) << 4)); //写月 DS1302_WriteByte(ADDR_WEEK, time->week); //写周 DS1302_WriteByte(ADDR_YEAR, (time->year % 10) | ((time->year / 10) << 4)); //写年 DS1302_WriteByte(ADDR_WP, 0x00); //退出写保护模式 } void DS1302_GetTime(time_t *time) { time->second = (DS1302_ReadByte(ADDR_SECOND) & 0x0F) + ((DS1302_ReadByte(ADDR_SECOND) >> 4) * 10); //读秒 time->minute = (DS1302_ReadByte(ADDR_MINUTE) & 0x0F) + ((DS1302_ReadByte(ADDR_MINUTE) >> 4) * 10); //读分 time->hour = (DS1302_ReadByte(ADDR_HOUR) & 0x0F) + ((DS1302_ReadByte(ADDR_HOUR) >> 4) * 10); //读时 time->day = (DS1302_ReadByte(ADDR_DATE) & 0x0F) + ((DS1302_ReadByte(ADDR_DATE) >> 4) * 10); //读日 time->month = (DS1302_ReadByte(ADDR_MONTH) & 0x0F) + ((DS1302_ReadByte(ADDR_MONTH) >> 4) * 10); //读月 time->week = DS1302_ReadByte(ADDR_WEEK); //读周 time->year = (DS1302_ReadByte(ADDR_YEAR) & 0x0F) + ((DS1302_ReadByte(ADDR_YEAR) >> 4) * 10); //读年 } ``` 请注意,这只是代码的基础框架,你还需要根据自己的实际情况进行适当的修改,例如时钟的初始化、引脚的设置等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值