第三天
第1节:STM32通用定时器原理介绍
定时器本质就是计数器
高级定时器、通用定时器和基本定时器。
1、高级定时器包含了通用定时器和基本定时器的功能,高级定时器是有四个捕获/比较通道,支持定时功能、输入捕获和输出比较功能、刹车以及互补输出功能,是一个 16 位可以向上/下计数的定时器。
2、通用定时器的功能比高级定时器简单,主要差异点在通道总数、互补输出通道组数、刹车功能。
3、基本定时器是一个只能实现定时功能、没有外部接口的定时器。类似CPU内部的系统定时器(SysTcik),不过强一点。
1.定时器
STM32F103C8T
C:48脚,8:64k,T:封装 T = LQFP
时基单元
计数器(定时器的本质计数):都是16位,最大计数65536(2的16次方)
预分频器:对时钟信号提前分个频,它相当于一个除法器(时钟信号频率太高,分个频,就是对频率进行除法,两分频:原本时钟信号72MHZ,则变为36MHZ)
倍频器:时钟信号增加倍数(你本来是72兆,两倍频就等于72×2=144MHZ)
计数模式:计数器是怎么样来计数,高级和通用都有三种
向上:零往上加,0->1->2->3->4->....;向下:也可以从初值往下减,10->9->8->7->...;中央对齐:先往上加,加到初值,再从初值往下减,0->1->2->3,3->2->1->0;
(如系统定时器,计数器是向下减,给它一个初值,从初值开始来一个脉冲减一个一直减到零,然后产生一个中断请求标志位)。
通道
输入通道:它所记的数,来自单片机引脚外部的,外部有几个通道
捕获比较通道:捕获信号然后比较信号
输出通道:往外输出数据的,比如说PWM输出一个脉宽调制信号,出口
.....视频更新了,内容差不多,多看手册,手册里都有
拆分为1-1节和1-2节
第1-1节:STM32定时器基础知识
定时器本质就是计数器
定时器其实就是一个闹钟,定时时间到,单片机去做一件事情。
1.方波 2、周期信号 3、时钟信号 4、计数器
1.方波:高电平和低电平组成
2、周期信号:重复出现的信号,单位时间1s重复的次数频率(周期的倒数)。T=高电平时间TH + 低电平时间TH;
根据这个高电平(低电平)多长时间出现一次T,再根据高电平出现的次数N,就可以知道时间过去了多久(T*n)。定时时间t:只需要用这个时间,除以周期就可以得出一个次数,就是高电平出现的次数(t/T=N),数这个次数就得到我们需要的时间
3、时钟信号:周期性方波
4、计数器:(数出现的次数)
5.定时器分类(看数据手册)
STM32F103C8T;中等
C:48脚,8:64k,T:封装 T = LQFP
计数模式:计数器是怎么样来计数,高级和通用都有三种
向上:零往上加,0->1->2->3->4->....;向下:也可以从初值往下减,10->9->8->7->...;中央对齐:先往上加,加到初值,再从初值往下减,0->1->2->3,3->2->1->0;
(如系统定时器,计数器是向下减,给它一个初值,从初值开始来一个脉冲减一个一直减到零,然后产生一个中断请求标志位)。
6、定时器结构简图
定时时钟电路:它就是用来产生特定周期的方波。(方波周期是可以设置,也就是频率是可以设置)
主模式控制器:控制其他电路的,控制定时器外部电路的。
从模式控制器:其他电路通过从模式控制器来控制计数器电路做复位、使能、计数、向上、
向下,五个功能。
计数器电路:对高电平进行计数,统计数了多少次,一次是多长时间(就可以知道总共过去多长时间:n*T,次数*高电平出现的时间(周期))
通道输入电路:输入
通道输出电路:输出
捕获比较电路:跟计数器进行比较,当他比较成功之后。如:当计数器大于多少的时候,可以输出一个信号。
编码器接口:来给波形进行编码
7、定时器常见功能
定时功能
PWM输入测量: PWM周期、频率、占空比((TH/T)*100%,高电平在周期中占的时间*100%,70%:高电平TH 7s,低电平TH 3s,7/(7+3)*100%=70%)测量;对外部输入进来的PWM信号进行测量(PWM:方波)
PWM波形输出(脉宽调制): 控制电机调速,开关电源电压调节
编码器接口:测速,通过编码器输出的这个信号,测量这个信号并且算出速度;知道这个速度后可以通过PWM调节这个速度
第1-2节:STM32定时器定时原理介绍
一、常见功能(四种)
定时功能
PWM输入测量: PWM周期、频率、占空比((TH/T)*100%,高电平在周期中占的时间*100%,70%:高电平TH 7s,低电平TH 3s,7/(7+3)*100%=70%)测量;对外部输入进来的PWM信号进行测量(PWM:方波)
PWM波形输出(脉宽调制): 控制电机调速,开关电源电压调节
编码器接口:测速,通过编码器输出的这个信号,测量这个信号并且算出速度;知道这个速度后可以通过PWM调节这个速度
二、定时器定时功能
解释:通过计数来实现定时,记一个数需要多长时间,将时间转换成计数的个数,然后通过计数器来对这个时钟信号进行计数,实现定时。
工作原理:
首先要选择这个时钟信号,必须选择一个周期信号,对这个周期信号要算出它的周期,一个周期有一个高电平,对高电平进行计数,多少高电平就表示过去多少个周期。(周期、频率互为倒数)
首先周期等于多少?算出记一个数的时间(核心)
1.算出计数器CNT它的频率,首先选出内部时钟(72MHZ),分频分成1MHZ(72分频),通过预分频器来进行对频率进行降频。预分频器它有个寄存器叫PSC(预分频寄存器),
2.比如你要定时20us,t/T=N:20/1=20,相当于计20个数,启动计数器,当计数器记到20,表示20us时间,这个时候可以产生一个中断请求,然后在中断里面写一个程序,控制蜂鸣器响,实现定时闹钟。
3.计算到20,产生一个更新事件,可以产生一个更新请求,如果用来申请中断(更新中断请求),申请中断会有相应的中断标志位产生(0->1),根据更新中断请求标志位,判断是否有更新事件产生(有好几种中断,我们判断更新中断请求标志位为1,表示向上溢出产生的中断事件)。
4.中断请求允许开关,开关打开,更新中断请求,就可以通过这个开关到达中断通道。然后设置中断(分组、优先级、开启通道)
5.计数器开启,用计数器就开计数器
计数个数N,储存在自动装载寄存器ARR,比如我们计数20,ARR就写19,
自动装载寄存器ARR:ARR+1=N,因此ARR=N-1。ARPE=0表示使用ARR
自动装载影子寄存器PARR:ARR的备份,ARPE=1表示使用PARR,在PARR计数时,改变ARR也不会改变PARR。可以通过ARR间接改变PARR,当PARR溢出时(记到20),PARR就会变为ARR的值(产生更新事件时修改其值为ARR的值)。ARR可以随时修改,PARR只能更新事件后修改。
注意:为什么要用PARR?当我们ARR记到5(原本为19),此时修改ARR为2,会导致ARR一直计数,记不到2,防止计数中乱修改ARR的值。如果我们用PARR,中途你改ARR也不会影响计数。
溢出(向上/向下):比如计数20,则ARR写19,类似一个容器,装19份水满(计数到19),当我们装到20时溢出,当计数器溢出,我们可以设置就是是否能够产生更新事件,更新事件其实就提示我们这个容器已经满了,换一个新容器(相当于CNT复0)
计数器的启动开关:开始定时就启动
注:根据公式Fpsc/(PSC+1) = Fcnt 与 Fcnt= 1/T
则72分为1MHZ,72/(71+1)=1MHZ,1MHZ算出Fcnt= 1/T -> Fcnt = 1/1 =1周期1us一个脉冲,一个数1us,20个数20us
注:(PSC+1)?表示频率分成几份,72MHZ中PSC=1时一条线切1刀分成2份则两个36MHZ(类似72/3=24);72MHZ中PSC=2时一条线切2刀分成3份则3个24MHZ(类似72/3=24)。因此就是你要分多少频率,PSC加一用它做除法好。
三、定时器相关寄存器
计数器寄存器(TIMx CNT):每记一个数,寄存器里面的数据就会变化(16位65535)
预分频器寄存器(TIMx PSC):分频的(16位65535)
自动装载寄存器(TIMX ARR):计数个数检验,简单地看成叫初值寄存器,存放初值(N-1)
控制寄存器 1(TIMx CR1):设置定时器的工作模式(不改就是默认) :启动,设置计数方向,等相关
中断使能寄存器(TIMX DIER):设置中断请求允许开关
SR:存放中断请求标志位,有请求产生的时候么,对应的请求有个标志位,在中断处理函数里面需要进行判断
四、初始化步骤
1、选择时钟源,内部时钟(周期信号)
打开定时器内部时钟APB2CLK(APB1:TIM2-7,APB2:TIM1,TIM8)
2、设置预分频器:内部时钟72MHZ,然后72分频得到1MHZ,1us=1个脉冲(1us一个数)
3、设置计数方向:默认计数向上计数,往上加
4、设置自动装载寄存器(ARR=N-1):记多少个数,存放初值,同时让ARPE=1,使用影子寄存器,防止中途修改ARR值导致计数错误。
5、设置中断:分组,中断优先级,中断允许使能(中断请求允许)打开中断通道
6、编写中断处理函数:判断是那个中断,并且设置定时到,要干嘛
7、打开定时器:启动计数器开始计数
第1-3节:STM32定时器定时10ms_寄存器
实现目标:
代码流程框图:
代码:
#include "main.h"
#include "stm32f10x.h" // Device header
#include "Led.h"
#include "Delay.h"
void Timer4_Init(void);
int main(void)
{
Led_Init();
Timer4_Init();
//TIM4->CR1 |= 1<<0;
TIM4->CR1 |= TIM_CR1_CEN;
while(1)
{
}
}
void Timer4_Init()
{
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
//TIM4->CR1 |= 1<<7;//寄存器被装入缓冲器
TIM4->CR1 |= TIM_CR1_ARPE;
//TIM4->CR1 |= 1<<2;//计数器溢出/下溢才产生更新中断
TIM4->CR1 |= TIM_CR1_URS;
TIM4->PSC = 72-1;
TIM4->ARR = 10000 - 1;//10us
//TIM4->DIER |= 1<<0;//允许更新中断
TIM4->DIER |= TIM_DIER_UIE;
//设置中断 1.默认4组 2.优先级 3.中断通道使能
NVIC_SetPriority(TIM4_IRQn,2);
NVIC_EnableIRQ(TIM4_IRQn);
}
void TIM4_IRQHandler(void)
{
// GPIOB->ODR |= (1<<14);
// Delay_ms(1000);
// GPIOB->ODR &= ~(1<<14);
static uint16_t T_Cnt =0 ;
if(TIM4->SR & TIM_SR_UIF)//更新事件的标志位
{
if( TIM4->DIER&TIM_DIER_UIE )//更新事件中断请求被允许
{
TIM4->SR &= ~(1<<0);//清除标志位
T_Cnt++;
if(T_Cnt >= 50)//50*10us=500us
{
T_Cnt =0;
if( GPIOB->ODR&(1<<14) )
{
GPIOB->ODR &= ~(1<<14);
}
else
{
GPIOB->ODR |= (1<<14);
}
}
}
}
}
错误总结:
1.点灯查电路
2.点灯查一段一段来查代码语法,看那个函数行那个函数不行
改这里闪烁速度没变?意思是说?
if(T_Cnt >= 50)//50*10us=500us
这里没删除,傻逼了
傻逼2,控制PB14,翻转这里写PB13
第4节:数码管基础知识
结构图
数码管:由八段LED组成,称作八段数码管,七个横条加一个小数点。
:10个引脚,1-5和6-10
内部结构解析
内部结构:8个LED灯,3,8是都是负极,10、9、7、5、4、2、1、6都是正极。低电平接地,正极高电平即可点亮,8个正极正好用一个字节(八位)表示八只脚的状态,若1000 0101则A、F、DP都被点亮了,共阴数码管(共阳同理),连在一起的是公共端(位选)。八个LED它的引脚叫正极(段),A、B、C、D、E、F、G、DP段,控制数码管和控制led它是一样的控制方式。
本质核心:控制数码管就是控制led。
“A”最低位,小数点“.”最高位
共阳极点亮数字1:点亮1就是点亮C、B,编码:1111 1001(翻转1001 1111)
四位数码管
(共阴端)内部图
8位的控制段(数据端送编码) + 4位选(4个数码管亮灭(公共端)) = 12引脚
共阴数码管:你单片机输出一个字节的数据,如果你要想让第1数码管显示,那么你让它的公共端(12)等于0(其余给1),数据端送数字即可;第2个数码管则,公共端9为0(其余给1),数据端送数字即可;
动态扫描:数码管1、2、3、4轮流点亮,公共端轮流低电平。
注意:动态扫描的时间越短,切换的时间越快,可以让人眼反应不过,展现出同时点亮(显示)的效果(节约引脚)
视频由照片组成的,一秒拍多张图片,然后让图片快速切换,形成视频
数码管编码
一个字节8bit,0000 0000,对应0XFF,正好一个16进制的数对应4bit。
第5节:数码管驱动芯片TM1640简介
一般用三极管来驱动数码管,一个三极管驱动一个数码管,数码管在比较少的时候可以用三极管,多的时候不适用(16个数码管需要16个三极管),数量多的时候用专门的芯片TM1640
数据手册如何阅读?
拿到一款芯片,我们首先要做的就是去把芯片的手册翻出来。然后按照下面的步骤来阅读数据手册。
第一步: 查看芯片的供电电压以及 IO 的电平电压(在电气特性部分查看)
第二步: 查看芯片的 IO 引脚功能(在管脚功能定义表格查看)。
第三步: 查看芯片的时序特性,这里描述了时钟频率,信号传输的上升沿下降沿等参数要求,以及电平保持时间。
第四步: 查看通信接口介绍,这里介绍了芯片与单片机连接的方式。比如芯片的SPI 接口,IIC 接口,单总线接口等。对应的接口数据传输时序图很重要,这个数据传输时要是描述总线接口是如何传递数据的,这属于硬件接口层。很多芯片接口时序差不多,这里 TM1640 是类 IIC 接口,不是标准的 IIC 接口。
第五步:查看芯片读写时序图(通信协议),芯片读写要按照通信协议,通过接口时序来传输,属于应用层数据读写。比如,如何往 TM1640 的缓存区写入显示数据。大部分芯片都是通过命令码的格式来传递来进行数据读写的。
比如,命令码(读还是写)+功能码 (实现什么功能)+数据。
第六步:查看芯片的功能码,实际上和第五步可以合并起来看通过发送不同的功能码来实现不同的功能。这一步结合第五步。
解读1:
看工作电压,引脚电平电压看电压范围是否一致;
分清楚引脚功能;
时序(时钟和数据),单片机与芯片如何传输一字节数据,时钟信号他要求的频率是多大,时钟信号一定要符合它的频率,数据数字 1 、0它是怎么表示(上升下降沿表示),电平保持时间,高电平呢要保持一段时间,因为你如果保持的时间太短了,芯片它可能来不及读(时间符合要求);
看它的通讯接口,什么串口、I2C、SPI协议等,简单来讲就是它连了几根线,每一根线是表达的是什么意思,是时钟线还是数据线,数据线是一根还是两根;
解读2:
针对第三天第5节:数码管驱动芯片TM1640简介_哔哩哔哩_bilibili
5:30分开始
第一步(工作电压等):
电气参数
极限参数:
工作电压 -0.5 ~ +7.0
逻辑输入电压:IO口,单片机输出信号(电压),1640则输入这个信号(电压)
正常工作:
典型电压
高电平的输入电压:0.7VDD~VDD 低电平的输入电压:0~0.3VDD
电气特性:
驱动能力(能够驱动多大的数码管):
能够驱动多大的数码管,SEG(段,LED灯)脚驱动拉电流,GRID(公共端,位)脚驱动灌电流;SCLK时钟线0.7VDD,DIN数据线0.3VDD
开关特性:
最大时钟频率1MHZ,时钟信号不要大于1兆,大于1兆我们这个1640芯片就反应不过来了
时序特性:
时钟脉冲宽度(时钟信号高电平的宽度),大于0.4微秒,小于0.4微秒无法识别;
数据建立时间(保持时间),数据从建立到稳定的时间,数据只有稳定了,才能读取
注意:主要就是两点,一个是电源的工作电压,还有一个就是高低电平那个电压范围。
第二步(引脚功能与连接MCU):
引脚:28个
辉度调节电路:亮度
两线串行接口(SCLK,DIN):时钟和数据
封装:
管脚功能定义:
和单片机如何连接:
黑色粗的是总线表示连接在一起,
单片机开漏模式正好两个上拉电阻,通用、开漏、输出模式
第三步(时序):
硬件时序(硬件驱动时序):单片机如何把一个八位的数据(一个字节的数据),送到外部芯片上面去。光有驱动还是不行,还有应用软件有个APP,没有鼠标也不行。如:摄像头有驱动,显示界面相当于应用程序,应用程序通过驱动来控制硬件设备。
先从上到下,再从左到右看:
Start:SCLK和DIN刚开始都是高电平一段时间(delay),然后数据线先拉低,delay一段时间,时钟线再拉低。
End:SCLK和DIN先是低电平一段时间(delay),然后时钟线拉高,delay一段时间,数据线再拉高。
通信协议:跟驱动协议不一样,是说一个数据包(由驱动组成)是如何传送,应用层的通讯时序
数据包:Command1、Command2包头,数据data1~N(1-16,16个数码管),包尾Command3
Command1:数据命令设置(工作模式) 01、显示控制命令(亮度)10
Command2:地址命令设置(数据发送到地址) 11
数据data1~N:真正的数据
第6节:单片机驱动模型与TM1640驱动程序编写
1、如何看数据手册
1、芯片的工作电源以及输入电平的电压范围
2、看芯片每只引脚的功能
3、看芯片与单片机的连接方式
4、查看芯片工作的极限参数
5、看接口时序与通讯时序
2、驱动结构分层
1、硬件接口层(单片机与芯片的连接方式)
2、接口时序层(单片机与芯片之间如何传送一个字节)
3、通讯协议接口层(芯片功能实现的通讯协议--暗号,按照规律将编码或者命令组成数据包)
4、应用程序实现层 (数据结构构建--数据编码,拆分,组合到数组里)
table[10]=(0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f.
TM1640要显示一个数据,它首先要把这个数据进行拆分,如显示1234,首先把1234拆分成四部分(千、百、十、个位),每一位数字对应一个编码,数码管显示是需要编码的。应用程序需要显示一个数据,第四层首先要把这个数字进行拆分,转换成数码管的编码,然后再把这个编码存在一个地方。第三层就是来读这些编码,并把这些编码按照规定好的协议发送出去,究竟如何发送。这就是第二层要做的就是把编码一个字节,一个字节的传输出去,第二层只负责发送一个字节,至于这些字节按照什么规律来排布,至于这些字节按照什么规律来排布是第三层的事。第一层单片机与芯片两个引脚连接来具体实现。
总结:
第四层拆分数据,并放到数组里,第三层把拆分的数据按照规律编写成一个数据包(这些数据包是由多个字节组成),数据包通过第二层时序层发送每一个字节的数据,最后,通过第一层单片机与芯片两个引脚连接来实现。
代码流程框图:
代码:
TM1640.c
#include "TM1640.h"
#include "stm32f10x.h" // Device header
#include "GPIO.h"
#include "Delay.h"
#define SET_SCLK ((GPIOB->ODR)|=(1<<8))
#define CLR_SCLK ((GPIOB->ODR)&=~(1<<8))
#define SET_DIN ((GPIOB->ODR)|=(1<<9))
#define CLR_DIN ((GPIOB->ODR&=~(1<<9)))
//TM_SCLK PB8
//TM_DIN PB9
//1层
void TM1640_GPIO_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRH &= 0XFFFFFF00;//PB8,PB9
GPIOB->CRH |= (GPIO_OUTPUT_OD + GPIO_SPEED_10MHZ)<<(8-8)*4;
GPIOB->CRH |= (GPIO_OUTPUT_OD + GPIO_SPEED_10MHZ)<<(9-8)*4;
GPIOB->ODR &= ~(1<<8);
GPIOB->ODR &= ~(1<<9);
}
//2层
void TM1640_Start(void)
{
SET_SCLK;
SET_DIN;
Delay_us(2);
CLR_DIN;
Delay_us(2);
CLR_SCLK;
}
void TM1640_Stop(void)
{
CLR_SCLK;
CLR_DIN;
Delay_us(2);
SET_SCLK;
Delay_us(2);
SET_DIN;
}
void TM1640_SendOneByte(uint8_t data)
{
//判断data最低位
for(uint8_t i=0; i<8; i++)
{
CLR_SCLK;//时钟低,DIN可变;即时钟低时传输输出,时钟高时读取
if( (data&0x01) )//数据是1
{
SET_DIN;
}
else//数据是0
{
CLR_DIN;
}
SET_SCLK;//时钟高,1640读取
Delay_us(2);//读取数据需要时间,也就是脉冲持续时间
data = data>>1;//由低位往高位读取;读取完毕,data的下一位移到最低位
}
}
//开启,传输,结束的封装函数
void TM1640_Write(uint8_t data)
{
TM1640_Start();
TM1640_SendOneByte(data);
TM1640_Stop();
}
//第2层,结束
//第3层,通信协议层
//command1
#define INC_Addr 0X40 //0100 0000 地址自动增加
#define NO_INC_Addr 0x44 //0100 0100 固定地址
void TM1640_Init(void)
{
TM1640_GPIO_Init();
TM1640_Write(INC_Addr);//command1 设置读数据指令:地址自增
TM1640_Write(Light_14_16);//command3 控制显示:亮度
}
//实现第3层的传输,command2的两部分
void TM1640_WriteArray(uint8_t *InputData)//*InputData:8个数码管的数据 command2 传显示的数据
{
TM1640_Start();
TM1640_SendOneByte(data_addr);//command2第1部分:设置地址
for(uint8_t i=0;i<EIGHT_SEGMENT_LENGTH;i++)//8个数码管,command2第2部分:传输显示数据
{
TM1640_SendOneByte(*InputData++);//command2
}
TM1640_Stop();
}
//第3层结束
//第4层定义
uint8_t table[10] = {0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F};//0-9对应编码
union EightSegment EightSegmentCode[EIGHT_SEGMENT_LENGTH]={0};//联合数组存储8位数码管的数据(8段代码,显示的数据
//第4层开启
//数据拆分(左边4位,右边4位),如显示1234,要把这个1234拆分成(1,2,3,4)
void show_data(uint16_t L_data,uint16_t R_data)
{
//L_data
EightSegmentCode[SEG1].uint8_Led=table[L_data/100/10];
EightSegmentCode[SEG2].uint8_Led=table[L_data/100%10];
EightSegmentCode[SEG3].uint8_Led=table[L_data%100/10];
EightSegmentCode[SEG4].uint8_Led=table[L_data%100%10];
//R_data
EightSegmentCode[SEG5].uint8_Led=table[R_data/100/10];
EightSegmentCode[SEG6].uint8_Led=table[R_data/100%10];
EightSegmentCode[SEG7].uint8_Led=table[R_data%100/10];
EightSegmentCode[SEG8].uint8_Led=table[R_data%100%10];
}
TM1640.h
#ifndef __TM1640_H
#define __TM1640_H
#include "stdint.h"
//command3 控制显示:亮度与开关
#define Light_1_16 0X88+0 //1000 1000
#define Light_2_16 0X88+1 //1000 1001
#define Light_4_16 0X88+2
#define Light_10_16 0X88+3
#define Light_11_16 0X88+4
#define Light_12_16 0X88+5
#define Light_13_16 0X88+6
#define Light_14_16 0X88+7
#define Light_OFF 0X80+0 //1000 0000
#define Light_ON 0X80+8 //1000 1000
//command2 设置地址:数码管地址
#define data_addr 0XC0 //数码管地址,显示地址 1100 0000
//16数码管地址,偏移
#define SEG1 0
#define SEG2 1
#define SEG3 2
#define SEG4 3
#define SEG5 4
#define SEG6 5
#define SEG7 6
#define SEG8 7
#define SEG9 8
#define SEG10 9
#define SEG11 10
#define SEG12 11
#define SEG13 12
#define SEG14 13
#define SEG15 14
#define SEG16 15
//数码管数量
#define EIGHT_SEGMENT_LENGTH 8
union EightSegment //8段
{
uint8_t uint8_Led;//8段同时操作
struct
{
uint8_t A:1;
uint8_t B:1;
uint8_t C:1;
uint8_t D:1;
uint8_t E:1;
uint8_t F:1;
uint8_t G:1;
uint8_t H:1;//8段,每1段进行操作
}bit;
//数字显示只用7段,小数点单独
struct
{
uint8_t ABCDEFG:7;//7段同时操作,小数点单独操作
uint8_t DP:1;
}bits;
};
extern uint8_t table[10];//0-9对应编码
extern union EightSegment EightSegmentCode[EIGHT_SEGMENT_LENGTH];//数组存储8位数码管的数据(8段代码)
void TM1640_Init(void);
void show_data(uint16_t L_data,uint16_t R_data);
void TM1640_WriteArray(uint8_t *InputData);//把数组中的数据发送到1640
#endif
main.c
#include "main.h"
#include "stm32f10x.h" // Device header
#include "Led.h"
#include "Delay.h"
#include "stdint.h"
#include "TM1640.h"
uint16_t L_data=0,R_data=9999;
int main(void)
{
Led_Init();
TM1640_Init();
while(1)
{
show_data(L_data++,R_data--);
TM1640_WriteArray((uint8_t*) (EightSegmentCode));//把数组中的数据发送到1640
if(L_data == 9999)
{
L_data =0;
}
if(R_data == 0)
{
R_data =9999;
}
Delay_ms(1000);
}
}
错误总结:
首先因为库函数实现成功了,所以硬件肯定没问题。
再加上GPT给错了六个可能错误的地方,然后自己排除,加上根据现象引导GPT,一起排除出最后找出问题所在。
现象1,数码管没亮,TM1640初始化没成功或者没打开灯。大概是第一层或者第二层出错。解决参考:错误1
现象2,数码管数组没变化都是0,拆分数据失败或者没有传输拆分后的数据,大概率第四层和第三层。解决参考:错误2
错误1
在TM1640_SendOneByte
函数中,在发送每一位数据后都将数据右移一位。但是,没有更新data
变量。应该在for
循环的最后添加data = data >> 1;
修正后:
错误2:
TM1640_WriteArray
函数中的for
循环在每次迭代时都发送相同的字节,因为InputData
指针没有移动到下一个字节。你应该在for
循环的最后添加InputData++;
第7节:STM32前后台系统的使用
前后台系统:基于软件定时器软件定时器,就是用软件实现定时器的功能
软件实现定时器:它的原理就是根据中断次数来实现的
定时器本质就是计数器
软件定时器它有什么特点:向下计数
软件定时器,根据定时实现多个任务,重复的代码和判断会导致代码冗余,因此可以用结构体,封装要判断的值,如GPIO闪烁定时值、动态扫描定时值等等。封装起来,通过判断结构体来实习具体的LED闪烁与动态扫描等
指针函数:返回值指针
函数指针:函数的地址
结构体类似如下:
然后我们用这个结构体类型呢定义一个数组,这个数组它就是用来储存任务相关的信息。
核心代码:
用来实现我们刚刚说的那段代码,软件定时中断
核心函数,具体代码如下:
当前系统里面任务的总数量
通过for循环去判断每一个任务的计数器,先统一减10,然后再判断判断是否等于零,等于0则计数器赋为重装载定时器的值
紧接着通过指针执行中断服务函数
这句话意思,计数器值大于初值,直接重新赋值(向下计数)。例如,当初周期500ms,计数器记到400ms,但是我们改变了周期为300ms,则此时重新赋值
结构体数组:储存任务的信息
新建任务与删除任务函数。
新建任务函数,参数(任务编号,任务初值就是定时值,定时到执行任务指针)
后台处理函数,专门处理后台的,处理时间和任务调用。
此函数执行一次,所有的定时器它的时间都会减十
任务函数,如定时时间到,执行led闪烁
LED闪烁案例:
复位函数:复位时间用,任务执行中途你要改变任务的定时时间。如定时时间是5S执行到3S时候,你改成2S则直接从2S开始计时
该初值函数,一般改完初值后还得复位,逻辑连贯
前台:while主循环,当多个任务,且中断执行代码执行时间较长,中断会调用多次,时间错乱导致错误,因此在中断里写上简单的标志位赋值,然后再用while循环来执行任务。保证程序正确运行。
后台判断时间是否到,前台执行相应的任务
结构体补充:标志位:时间到。任务优先级:多个任务同时到零
多个任务如何判断?优先级高的任务先减10,然后判断其是否为0,并且执行其软件中断函数。不需要根据这个数组编号来判断,根据中断优先级来判断即可。(需要修改如下函数)
如果我们的某个编号,如ID编号为3的任务时间较长,我们可以直接判断其编号是否为3,是则直接改变标志位,然后再while查询标志位。