蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

这篇具有很好参考价值的文章主要介绍了蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

谨以此文和我去年前的一篇蓝桥杯单片机的教程构成电子类的青铜双壁.

国信长天单片机竞赛训练之原理图讲解及常用外设原理(遗失的章节-零)_昊月光华的博客-CSDN博客

 

 

目录

时钟树

串口重定向:printf输出

动态点灯(点灯大师)

按键(常用状态机)

同一时刻对多个按键按下进行判断

系统滴答定时器Systick(了解)

*ADC的DMA多通道采样+平均滤波处理(多通道使用)

*串口的DMA+空闲中断不定长接受任意类型的数据(高效且简洁)

定时器通道的输入捕获(采集频率和占空比)

运行期间动态更改PWM的频率

PWM波的生成

IIC读写epprom

扩展板之ds18b20温度传感器

扩展板之dht11温度传感器

扩展板之三位数码管

扩展板之ADC按键

扩展板之三轴加速度传感器

扩展板之光敏电阻DO&AO

扩展板之两路AD采集

 扩展板之频率测量PUS1,PUS2,PWM1,PWM2


时钟树

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

串口重定向:printf输出

注意:

  1. 勾选微库.若发现根本进不了main函数里面一般是这个原因.
  2. 包含#include "stdio.h"

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

int fputc(int ch,FILE * f )
{
    HAL_UART_Transmit(&huart1,  (uint8_t *)&ch,1,0xff);
    
    return ch;
    
}

动态点灯(点灯大师)

u8 LEDINDEX[]= {0X00,1<<0,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};
u8 LEDDT[]={0,0,0,0,0,0,0,0,0};
void led_display(u8 ds)
{
    HAL_GPIO_WritePin(GPIOC,0xff<<8,GPIO_PIN_SET);
     HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
       HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
    
    
     HAL_GPIO_WritePin(GPIOC,ds<<8,GPIO_PIN_RESET);
     HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
       HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
     
}
void led_scan(){
    int ds = 0;
    for(int i = 0 ;i < 4;i++)
    {
        ds|=LEDINDEX[LEDDT[i]];
        
    }
    led_display(ds);
}

运行期间只需要更改LEDDT的值就行,LEDDT没有顺序,表示最多同时点亮的灯的数目。

按键(常用状态机)

(状态机,假设同时只有一个按键按下,无论长按还是短按都只判断按下,记作一次)这个代码只能一次性判断一个按键按下,无法对同一时刻多个按键按下进行判断,一般这个代码就够用了)

使用定时器7,配置为20ms进入一次回调函数。这种我认为是日常中比较常用的按键操作,因为以前对51单片机按键扫描的记忆,故回顾了下。

#define ReadB1  HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define ReadB2  HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define ReadB3  HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define ReadB4  HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
u8 key_state= 0;
u8 rkey = 0;
u8 key = 0;
void  HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
    if(htim->Instance == TIM2)
    {
        LedFlusht++;
        if(time++ == 1000)
        {
            time = 0;
            LEDDT[1] = LEDDT[1]?0:1;
            
            
        }
        
    
        
    }
    else if(htim->Instance == TIM7)
    {
        
        //执行按键扫描
        switch(key_state)
        {
            
            case 0:
                if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
                {
                    key_state = 1;
  
                }
                break;
            case 1:
                  if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
                {
                    key_state = 2;
                    if(!ReadB1) key = 1;
                    else if(!ReadB2) key=2;
                    else if(!ReadB3) key=3;
                    else if(!ReadB4) key =4;
                    rkey = key;
  
                }
                else 
                {
                    
                    key_state = 0;
                    key = 0;
                    
                }
                break;
            case 2:
                rkey = 0;//在只需要判断按下之后,不管长按短按(有没有松开)都试做按下一次
                if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
                {
                    
                }
                else 
                {
                    key = 0;
                    key_state= 0;
                    
                }
                break;
            
            
        }
        
        
        keyAction();
        
        
        
        
    }
    
    
}

同一时刻对多个按键按下进行判断

原理:把按键定义成一个结构体变量:


typedef struct Key{
    u16 keytime;
    u8 keystate;
    bool sflag;
    bool lflag;
    bool state;

}Key;

按键扫描:(按下小于500ms属于短按,大于500ms属于长按) ,按键扫描函数单独用一个定时器,每10ms产生一次中断去执行,这也就是为什么keytime+=10的原因.

#define ReadB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define ReadB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define ReadB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define ReadB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

 void key_scan()
{
    
    keys[0].state =  READB1;
    keys[1].state =  READB2;
    keys[2].state =  READB3;
    keys[3].state =  READB4;
    
    for(int i = 0 ; i < 4 ;i++)
    {
        switch(keys[i].keystate)
        {
            
            case 0:
                 if( !keys[i].state){
                     keys[i].keystate =1;
                     
                 }
                break;
            case 1:
                  if(!keys[i].state){
                     keys[i].keystate =2;
                     
                 }
                  else 
                      keys[i].keystate = 0;
                break;
            case 2:
                if(!keys[i].state)
                {
                    
                    keys[i].keytime+=10;
                   
                }
                else 
                {
                        if(keys[i].keytime > 500)
                        {
                            keys[i].lflag = 1;
                        }
                        else 
                            keys[i].sflag = 1;
                        
                        
                        keys[i].keytime = 0;
                        keys[i].keystate=0;
                        
                    
                    
                }    
                break;
            default:
                break;
            
            
        }   
    }  
}

//串口测试代码
void keywork(void)
{
    
    if(keys[0].sflag)
    {
        
        printf("0\r\n");
        keys[0].sflag = 0;
        
    }
    else if(keys[0].lflag)
    {
        
        printf("long 0\r\n");
        keys[0].lflag = 0;
    }
     if(keys[1].sflag)
    {
        
        printf("1\r\n");   keys[1].sflag = 0;
        
    }
    
    
      else if(keys[1].lflag)
    {
        
        printf("long 1\r\n");
        keys[1].lflag = 0;
    }
    
    
    
    
    
    
     if(keys[2].sflag)
    {
        
        printf("2\r\n");
           keys[2].sflag = 0;
    }
     if(keys[3].sflag)
    {
        
        printf("3\r\n");
           keys[3].sflag = 0;
        
    }
    
    
    
}

逻辑处理: 处理完后sflag (短按标志)或lflag(长按标志)记得清零处理。

系统滴答定时器Systick(了解)

HAL_GetTick() 函数是 STM32 微控制器 HAL(硬件抽象层)库提供的函数。它返回当前以毫秒为单位的节拍计数。默认情况下,系统定时器每1ms生成一个中断以更新节拍计数。因此,在大多数情况下,每个节拍的速率为1ms。

此函数可用于实现基于时间的操作,例如延迟、超时和任务调度。通过比较当前节拍计数与之前保存的值,可以确定自那个事件以来经过的时间。

例如,要创建100ms的延迟,您可以使用 HAL_GetTick() 保存当前节拍计数,然后在循环中等待,直到保存的节拍计数和当前节拍计数之间的差达到100ms。

uint32_t startTick = HAL_GetTick(); while ((HAL_GetTick() - startTick) < 100);

请注意,当节拍计数达到其最大值(0xFFFFFFFF)后,它会绕回,并且在计算长时间内经过的时间时必须考虑到这一点。

我们使用DHT11,DS18B20需要用到的us级延时就需要通过systick来实现。

HAL_GetTick(); 获取当前节拍.默认是1ms产生1个tick(节拍),则systick的计数值1ms发生溢出产生中断,

systick的默认中断函数:(每次中断溢出则增加节拍 ”Tick“)

Systick的主频:

SysTick定时器的主频取决于所使用的系统时钟(HCLK)频率和它的分频系数。在STM32微控制器中,SysTick定时器的时钟源可以配置为HCLK/8或HCLK。

如果SysTick定时器的时钟源被配置为HCLK/8,则SysTick定时器的主频将为HCLK/8。例如,如果HCLK的频率为72MHz,则SysTick定时器的主频将为9 MHz。

如果SysTick定时器的时钟源被配置为HCLK,则SysTick定时器的主频将为HCLK。例如,如果HCLK的频率为72MHz,则SysTick定时器的主频将为72 MHz。

需要注意的是,SysTick的主频越高,计数器溢出的时间间隔就越短,因此可以获得更高的精度和分辨率。但是,SysTick的主频和计数器位数的组合也会影响 SysTick 的最大定时周期。

systick的时钟源一般被设置为AHB总线上的时钟频率,比如常见的80MHZ.

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

配置systick的时钟频率 HAL_SYSTICK_CLKSourceConfig() 

两种选择 HCLK 或 HCLK/8 

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

 溢出时间判断:SYSTICK->LOAD的装载值为79999,所以:

产生一次中断的时间为:1/(80M-1)*(79999+1) =1/10^3 = 1ms

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

默认的SYSTICK中断函数:

/**
  * @brief This function handles System tick timer.
  */
__weak void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

/**
  * @brief This function is called to increment a global variable "uwTick"
  *        used as application time base.
  * @note In the default implementation, this variable is incremented each 1ms
  *       in SysTick ISR.
  * @note This function is declared as __weak to be overwritten in case of other
  *      implementations in user file.
  * @retval None
  */
__weak void HAL_IncTick(void)
{
  uwTick += uwTickFreq;
}

 把SYSTICK做定时用,重写systick的中断函数.(节省定时器,一般情况下,我们都不会这样去做,因为OS会把它作为时基,而我们的一些ms级延时也要得益改变systick的频率得到)

此时需要把原来的SysTick_Handler标记为weak。不好的地方在于每次生成代码都需要改.

这一点纯属当拓展知识去用,知道可以这么干就可以了.

void SysTick_Handler(void)
{
    
     HAL_IncTick();
    static uint32_t ick = 0;   // 定义静态变量tick,记录自系统启动以来经过的毫秒数
    ick++;                     // 每次中断触发时tick值加1
    if (ick == 1000) {         // 如果tick值等于1000,则表示已经过了1秒钟
        // TODO: 执行每秒钟需要执行的任务
        ick = 0;               // 将tick值重置为0,开始新的计数
        printf("hello world\r\n");
    }
}

*ADC的DMA多通道采样+平均滤波处理(多通道使用)

比赛实际上用单通道就可以,每次采样需要指定adc的通道.

STM32基于hal库的adc以DMA的多通道采样以及所遇问题解决_stm32 hal adc dma_昊月光华的博客-CSDN博客

*串口的DMA+空闲中断不定长接受任意类型的数据(高效且简洁)

(记得数据宽度设置为1个字节 8位)

若是hal库的版本并不是特别的新,那么还是用串口接受中断+空闲中断吧,我这边测试1.3的固件包是可以用DMA的那个空闲中断函数 ,如:

   HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx1Buf,200); //串口1开启DMA接受

基于HAL库的STM32的串口DMA发送数据(解决只发送一次数据)及DMA+空闲中断接受数据_hal 串口dma发送_昊月光华的博客-CSDN博客

定时器通道的输入捕获(采集频率和占空比)

对输入的脉冲进行捕获,算出求频率和占空比.设置定时器的频率的1M用来对输入的脉冲进行计数,假设输入脉冲一个周期的计数值为t(从一个脉冲的上升沿到下一个脉冲的上升沿).则频率计算为   1000000 /  t  .

比如对PA15信号发生器.

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

 配置(PA15接脉冲发生器测出来的占空比大概为50%。

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    
    static u16 t = 0;
    static u16 d = 0;
    if(htim->Instance == TIM2)
    {
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            LEDDT[0]=1;
            t = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1)+1;
            freq = 1000000 / t;
            duty = (float)d/t*100;
            
        }
        
        else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
              LEDDT[1]=2;
            d =  HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2)+1;
        }
        
        
    }
}

 通过按键打印

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

运行期间动态更改PWM的频率

pwm的频率 = 定时器的频率/autoreload的值.其中定时器的频率 = 时钟频率/分频系数.

之前参加过蓝桥杯单片机的相信有过经历:用定时器去实习pwm波:

记得我们是如何实现的?

假设定时器的频率是1M.每1us计数值加1,若设置为1000个计数值后溢出则产生一次中断,则1ms进入一次中断,在中断函数内,我们让其定义一个变量,让其加到200后归0,其中前100ms设置为高电平,后100ms设置为低电平,则周期为200ms,频率为1/200ms=5HZ,占空比为50%。(假设有效电平为高电平).

再回到STM32来,这里的autoreload就是计数值,一个计数的时间是1/定时器的频率。则PWM的频率为1  /   [autoreload*1/定时器的频率) ]  = 定时器的频率/ autoreload .证明完毕.

我们通过更改autoreload的值去动态的更改pwm的频率,需要注意的是当autoreload的值发生改变,需要重新去计算占空比。 占空比 = TIM15->CCR1 /autoreload .

//改变PWM的频率且稳定占空比保持不变 以TIM15->CCR1为例
void changePwmFreqAndkeepDuty(float duty,u16 autoloadval)
{
    
        extern TIM_HandleTypeDef htim15;
        TIM15->CCR1 = duty*autoloadval;
        printf("new freq: %d\r\n",10000/autoloadval);
        __HAL_TIM_SetAutoreload(&htim15,autoloadval-1);
        HAL_TIM_GenerateEvent(&htim15, TIM_EVENTSOURCE_UPDATE);
    
   
}

通过按键触发更改

按键触发的demo

if(keys[0].sflag)
    {
        
        static  u8 t =0;
        t++;
        static bool flag = false;
        if( t == 100) t=0;
         
       void changePwmFreqAndkeepDuty(float duty,u16 autoloadval);
        
        flag =!flag;
        if(flag)    changePwmFreqAndkeepDuty(0.8,50);
        else changePwmFreqAndkeepDuty(0.5,100);
     
        printf("0\r\n");
        keys[0].sflag = 0;
        
    }

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

PWM波的生成

相信看到这,PWM的产生原理已经很清楚了,用cubeMX配置将会更加清楚.

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

 一个定时器可以配置多个通道产生多组PWM波.

 开启PWM波:

   HAL_TIM_PWM_Start_IT(&htim15,TIM_CHANNEL_1);

更改PWM的值(如TIM15) TIM15->CCR1 =XXX (表示TIM15的通道1)

IIC读写epprom

需要注意在读数据时,需要主机发送应答信号表示。以及需要初始化.

 I2CInit(void);

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

//EEPROM读写代码
void eeprom_write(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	
	I2CSendByte(ucAddr);	
	I2CWaitAck();
	
	while(Size--)
	{
		I2CSendByte(*pucBuf++);
		I2CWaitAck();	
	}
	I2CStop();
	 
}

void eeprom_read(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	
	I2CSendByte(ucAddr);	
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);
	I2CWaitAck();
	
	while(ucNum--)
	{
		*pucBuf++ = I2CReceiveByte();
		if(Size)
			I2CSendAck();	
		else
			I2CSendNotAck();
	}
	I2CStop();	
}

扩展板之ds18b20温度传感器

驱动代码比赛官方会给出.只需要自己写一个读取温度的函数就行.给出的驱动有个不稳定的因素,那就是delay_us()这个延时函数没有重写。驱动是以80M做的一个微秒级延时,当在更改系统时钟频率时则有大问题(因为指令周期会随着系统的时钟频率而变化!),同样的,DHT11的驱动也有这个问题.,这完全是让学生专注于与逻辑业务的实现,驱动能跑就行(记得设置系统主频为80M).

资源包给的延时函数:

#define Delay_us(X)  delay((X)*80/5)

void delay(unsigned int n)
{
    while(n--);
}

跳线帽连接 P4 与 P3 的 TDQ进行连接.

main函数中调用初始化:

  ds18b20_init_x();

读取温度

float ds18b20_read(void)
{
    
    unsigned char  th,tl;
    unsigned short res;
    ow_reset();
    ow_byte_wr(0xcc);
    ow_byte_wr(0x44);
    
    
     ow_reset();
    ow_byte_wr(0xcc);
    ow_byte_wr(0xbe);
    
    tl =    ow_byte_wr(0xff);
    th =     ow_byte_wr(0xff);
    
    res=(th<<8)|tl;
    
    return res*0.0625;
    
    
    
}

扩展板之dht11温度传感器

每次读取完后的下一次读取则会失败,这不是我们的问题。只需要得到在它正确读取时的值就行。

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

dht11比赛赛点资料包(14届)给的驱动代码只给了2个函数,两个改变DQ输入输出的函数。。。

dht11_hal.h

#ifndef __DHT11_HAL_H
#define __DHT11_HAL_H

#include "stm32g4xx_hal.h"

#define HDQ		GPIO_PIN_7

 
 
		 

typedef struct {
	
    float temp;
    float humi;
    
}dht11Data;

void dht11Init(void);
int Readdht11(void);


#endif

dht11_hal.c

#include "dht11_hal.h"

#define  OUTDQLOW		HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_RESET)
#define OUTDQHIGH      HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_SET)
#define READDQ          HAL_GPIO_ReadPin(GPIOA,HDQ)
dht11Data dht11;

//
static void usDelay(uint32_t us)
{
	uint16_t i = 0;
	while(us--){
		i = 30;
		while(i--);
	}
}

//
void outDQ(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	
	GPIO_InitStructure.Pin = HDQ;
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
	
 
}

//
void inDQ(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.Pin = HDQ;
	GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}

//
void dht11Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
	outDQ();
    OUTDQHIGH;
}

//
uint8_t recData(void)
{
	uint8_t i,temp=0,j=220;
	
	for(i=0; i<8; i++){
		
		while(!HAL_GPIO_ReadPin(GPIOA,HDQ));
		usDelay(40);
		if(HAL_GPIO_ReadPin(GPIOA,HDQ))
		{
			temp=(temp<<1)|1;
			while(HAL_GPIO_ReadPin(GPIOA,HDQ)&&(j--));	
		}	
		else
		{
			temp=(temp<<1)|0;
		}
	}
	return temp;
}

//复位DHT11
void DHT11_Rst(void)
{
    outDQ(); 	//设置为输出
    OUTDQLOW;
    HAL_Delay(20);	//拉低至少18ms
    OUTDQHIGH;
    usDelay(60);     	//主机拉高20~40us
}

//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
unsigned char DHT11_Check(void)
{
    unsigned char re = 0;
    inDQ();    //设置为输入
    while (READDQ && re < 100) //DHT11会拉低40~80us
    {
        re++;
        usDelay(1);
    };
    if(re >= 100)return 1;
    else re = 0;
    while (!READDQ && re < 100) //DHT11拉低后会再次拉高40~80us
    {
        re++;
        usDelay(1);
    };
    if(re >= 100)return 1;
    return 0;
}


int Readdht11(void)
{
    
     unsigned char  buf[5];
     unsigned char  i;
    DHT11_Rst();
    if(DHT11_Check() == 0)
    {
        for(i = 0; i < 5; i++)
        {
            buf[i] = recData();
        }
        if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
        {
            dht11.humi = buf[0];
             dht11.temp =buf[2];
            
        }
    }
    else return 1;
    return 0;
     
}

扩展板之三位数码管

使用三个引脚去驱动三位数码管,采用SPI通信的方式

  • RCLK:  存储允许信号 低电平有效

RCLK是存储允许信号(Register Clock signal)。它控制数码管何时实际显示存储在内部寄存器中的数据。当RCLK信号拉低时,数码管会更新内部寄存器,存储要显示的数据。当RCLK信号拉高时,数码管会从内部寄存器中读取数据,并实际显示出来。

  • SCK: 时钟信号线 

在其下降沿时开始发送数据

  • SER

SER是段选通信号(Segment Enable Register)。它控制数码管的哪些段被选通发光。数码管是由多个发光二极管构成的,它包含a、b、c、d、e、f、g等7段,以及小数点段。要显示某个数字,需要选通对应数字的发光段。例如,要显示数字“2”,需要选通a、b、d、e、g几个段。

与数码管的通信逻辑: 每次在SCK为低电平时传输数码管的段选数据位,先传ds3,再ds2,最后ds1.传输的数据由SN74LS595N进行移位.

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

SN74LS595N芯片

        SN74LS595N是一种8位移位寄存器,也称为串行输入并行输出(SIPO)移位寄存器。它可以将接收的串行数据转换成并行数据输出。该芯片的主要特征有:- 8位移位寄存器,用于存储输入的串行数据并并行输出- 三态输出,可以直接驱动LED或数码管段选通- 简单的串行数据输入和时钟输入- 高电平电源:5V该芯片常用于LED点阵的驱动或数码管的段选通驱动。使用此芯片可以大大简化外围电路,减少芯片数量。

 关于为什么需要从第三位段选数据开始传输?

1. SN74LS595N是一种移位寄存器,它会将输入的串行数据依次存入8个寄存器位,并在RCLK信号的跳变沿将这8位数据并行输出。2. 数码管的段选通信号是并行的,依赖这8个输出的数据选择点亮相应段。3. 所以,第一组输入的8位数据会存入移位寄存器的最末8位,并第一个输出驱动第一个数码管。4. 然后第二组8位数据输入会将第一组数据移到更高8位,自己存入最末8位,并输出驱动第二个数码管。5. 以此类推,每输入一组8位数据,之前的输出数据会移位8位,新的8位数据输出驱动新的数码管。(seg信号->DH#1->DH#2)所以从第三位数码管的段选信号开始)

数码管的驱动以及定时动态扫描,只需要更改smgDT[ ] 数组内的值完成数码管的显示。 




#include "seg.h"


//
uc8 Seg7[17] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 0x00};

u8  SmgDT[] ={ 16,16,16};
void SEG_Init(void)
{

    GPIO_InitTypeDef GPIO_InitStruct ;
    __HAL_RCC_GPIOA_CLK_ENABLE();
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_RESET);
    GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

//
void SEG_DisplayValue(u8 Bit1,  u8 Bit2, u8 Bit3)
{
    u8 i = 0;
    u8 code_tmp = 0;
    RCLK_L;
    code_tmp = Seg7[Bit3];

    for(i = 0; i < 8; i++)
    {
        SCK_L;
        if(code_tmp & 0x80)
        {
            SER_H;
        }
        else
        {
            SER_L;
        }

        code_tmp = code_tmp << 1;
        SCK_L;
        SCK_H;
    }

    code_tmp = Seg7[Bit2];
    for(i = 0; i < 8; i++)
    {
        SCK_L;
        if(code_tmp & 0x80)
        {
            SER_H;
        }
        else
        {
            SER_L;
        }

        code_tmp = code_tmp << 1;
        SCK_L;
        SCK_H;

    }

    code_tmp = Seg7[Bit1];
    for(i = 0; i < 8; i++)
    {
        SCK_L;
        if(code_tmp & 0x80)
        {
            SER_H;
        }
        else
        {
            SER_L;
        }

        code_tmp = code_tmp << 1;
        SCK_L;
        SCK_H;

    }

    RCLK_L;
    RCLK_H;
}


void ScanDisplay(void)
{
    
    
    extern u8 smgt;
    if(smgt > 153 ) SEG_DisplayValue(SmgDT[0],SmgDT[1],SmgDT[2]),smgt  = 0;
 
}

扩展板之ADC按键

原理:通过读取adc的值(0-4096之间)来映射按键1到按键8。

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

 状态机判断adc按键单击操作

u8 getadcKey()
{
    
   // 这里的值完全可以试出来,我也记不住
    if(madc2 < 10)   return 1; 
	if(madc2 < 800)  return 2;
	if(madc2 < 1600) return 3;
	if(madc2 < 2000) return 4;
	if(madc2 < 2700) return 5;
	if(madc2 < 3300) return 6;
	if(madc2 < 3700) return 7;
	if(madc2 < 4000) return 8;
    
    
    return 0;
    
}


u8  readadckey()
{
     
    u8 static keystate =0;
      u8 temp  = 0;
      u8 val = 0;
       switch(keystate)
       {
           
           case 0:
                temp =getadcKey();
                if(temp)keystate = 1;
               break;
           case 1:
                temp =getadcKey();
                if(temp)keystate = 2,val = temp;
                else keystate =0;
               break;
           case 2:
                 temp =getadcKey();             
                 if(!temp)keystate =0;
 
               break;
           default:
               break;
           
           
           
       }
      
    
    return val;
     
    
}

扩展板之三轴加速度传感器

采用IIC通信协议. 注意,之前用epprom用的也是IIC,三轴加速度传感器的IIC的两根线:SCL,SDA是PA4,PA5.

mems.c

#include "i2c.h"
#include "mems.h"


s8 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);
}

//
s8 *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)
{
    //Power up (100Hz data rate)
    LIS302DL_Write(CTRL_REG1, 0x47);
    //HP filter bypassed
//    LIS302DL_Write(CTRL_REG2, 0x00);
//    //FF_WU 1 interrupt send to INT Pad1(open drain) active low
//    LIS302DL_Write(CTRL_REG3, 0xC1);
//    //threshold :0.5 g
//    LIS302DL_Write(FF_WU_THS_1, 0x28);
//    //filter :200ms @100Hz
//    LIS302DL_Write(FF_WU_DURATION_1, 40);
//    //ZLIE event
//    LIS302DL_Write(FF_WU_CFG_1, 0x10);
}

//
uint8_t LIS302DL_Check(void)
{
    if(LIS302DL_Read(0x0f))
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

mems.h

#ifndef __MEMS_H
#define __MEMS_H

#include "main.h"

#define CTRL_REG1 			0x20
#define CTRL_REG2 			0x21
#define CTRL_REG3 			0x22
#define FF_WU_THS_1 		0x32
#define FF_WU_DURATION_1 	0x33
#define FF_WU_CFG_1 		0x30
#define STATUS_REG 			0x27


uint8_t LIS302DL_Check(void);
void LIS302DL_Config(void);
s8  *Lis302DL_Output(void);

#endif

扩展板之光敏电阻DO&AO

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

 原理: 把RP7(电位器)处得到的电压值与3处的电压值进行比较器输出得到DO.AO则直接进行adc采样再数模转换得到电压。

DO:读取数字量,非常简单,直接读取PA3的引脚电平得出。通过转到电位器R7判断是否读取正确。

         if(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) )
        {
            LCD_DisplayStringLine(Line7, (u8 *)"       DO:High     ");
        }
        else
        {
            LCD_DisplayStringLine(Line7, (u8 *)"       DO:Low      ");
        }

AO:读取模拟量,本质就是读取adc的值.

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

扩展板之两路AD采集

蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱

这里用到的是同一个adc的两个通道,完全可以用我上面的adc的dma的多通道采集实现.扩展板给的例程代码相当于每次都初始化一遍adc,然后再单通道采集。这种方式好处在于易于理解,而用adc的dma多通道则方便多了。

给出非DMA的单通道采集,当多通道使用则每次更改adc的配置参数并重新初始化adc.

u16 getadc(u32 ch){ 
 
        ADC_ChannelConfTypeDef _adc = { 0 } ;
        _adc.Channel=ch;
        _adc.Rank=1;
        _adc.Rank = ADC_REGULAR_RANK_1;
        _adc.SamplingTime=ADC_SAMPLETIME_640CYCLES_5;
        _adc.SingleDiff = ADC_SINGLE_ENDED;
        _adc.OffsetNumber = ADC_OFFSET_NONE;
        HAL_ADC_ConfigChannel(&hadc2,&_adc);
        HAL_ADC_Start(&hadc2);
        return (u16)HAL_ADC_GetValue(&hadc2);
}

 扩展板之频率测量PUS1,PUS2,PWM1,PWM2

频率测量与上面定时器通道的输入捕获同理,可获取脉冲的频率和占空比.

需要注意的是多路输入捕获测量频率和多路捕获PWM的频率和占空比

基于HAL库的STM32单定时器多路输入捕获测量PWM的频率和占空比实现(状态机方式实现)_昊月光华的博客-CSDN博客

基于HAL库的STM32的单定时器的多路输入捕获测量脉冲频率(外部时钟实现)_昊月光华的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-467290.html

到了这里,关于蓝桥杯嵌入式STM32G431RBT6竞赛指南与模板——最后的绝唱的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [蓝桥杯嵌入式]STM32G431——第十二届第一场省赛停车计费系统真题及程序设计代码详解

    最近,我报名了今年的蓝桥杯嵌入式比赛,为此刷了一下以往的真题。以下是我对十二届蓝桥杯省赛真题的一些思路和心得,还有一些具体代码的实现。 1、相关模块 第十二届比赛主要用到的模块包括:LED、KEY、LCD、TIM、USART 2、重难点分析 这道题主要目的是做一个停车管理

    2024年01月18日
    浏览(96)
  • 蓝桥杯嵌入式STM32 G431 hal库开发速成——ADC与DAC

    模数转换器(ADC):它将模拟信号转换为单片机能够处理的数字信号。在很多应用中,比如温度传感器、压力传感器等,信号最初都是模拟形式的。ADC 读取这些模拟信号,然后将它们转换为数字形式,以便单片机可以读取和处理。 数模转换器(DAC):它执行相反的操作,将

    2024年02月01日
    浏览(63)
  • 蓝桥杯嵌入式CT117E-M4学习笔记02-STM32G431RBT6芯片学习

    首先学习了解一下蓝桥杯嵌入式CT117E-M4开发板的主控芯片STM32G431RBT6,本文仅为个人学习成果总结,如有错误,恳请指正。 上图为STM32CubeMX选型界面,如图可以看出STM32G431RBT6具有以下特点和硬件集成。 采用Cortex-M4 32位RISC核心架构,工作频率最高可达170Mhz。 128kBytes的FLASH,32

    2023年04月09日
    浏览(63)
  • STM32G431RBT6移植FreeRTOS

    引言: 本文专门为参加了蓝桥杯嵌入式赛道的同学准备, 大家可能会有这样一个问题, 比完赛之后, 对于像继续使用STM32G431RBT6学习FreeRTOS的, 发现网上的教程使用的板子基本上都是F1和F4的, 其实呢, 随便移植一下就能在我们自己的板子上面运行FreeTROS了。如果大家有AR

    2024年04月23日
    浏览(67)
  • 关于STM32G431RBT6的学习

    初入茅庐之第一次CubeMX的使用,工程结构框架的创建与LED的点亮。 温馨提示:本人第一次创作,也是第一次学习有关嵌入式的知识,本文内容均为个人见解,如有错误,欢迎指正。 首先打开STM32CubeMX,创建一个新的工程,点击File→New Project。 在Part Number中直接搜索STM32G431RB。

    2024年01月21日
    浏览(43)
  • STM32G431RB--基于HAL库(蓝桥杯嵌入式赛前梳理)

    明天就进行蓝桥杯的比赛了,最后一天再重新梳理一下各个模块的使用和代码的编写。 如果各个模块的MX配置是根据我之前发的来的,那么这篇文章中的代码完全适用;如不是,原理部分也是相同的,代码部分适用,可以自行判断,作为一个参考。 引脚: 1.控制LED灯亮灭时需

    2023年04月08日
    浏览(97)
  • 蓝桥杯嵌入式 STM32G4 MCP4017可编程电阻

    MCP4017为可编程电阻 查阅产品手册可知,MCP4017使用I2C通讯协议,引脚为PB6,PB7 ,控制的电阻连接至PB14,同时跳线帽连接J15.1与J15.2 如下图,Rs为单个电阻阻值;RWS为总阻值,与R17串联,两者对VDD电压进行分压, 可以通过测量PB14的电压判断可编程电阻的阻值。 MCP4017的默认总阻

    2023年04月08日
    浏览(63)
  • 专门针对数字电源相关应用,STM32G474CBT3/ STM32G474QET3/ STM32G474RBT3/ STM32G484CBT3带有DSP和FPU指令的混合信号MCU【嵌入式】

    STM32G4系列集成了运行于170 MHz的32位Arm® Cortex®-M4内核(支持FPU和DSP指令),以及3种不同的硬件加速器:ART Accelerator™、CCM-SRAM程序执行加速器,以及数学运算加速器。STM32G4系列还提供: 丰富的高级模拟外设(比较器、运算放大器、DAC) 支持硬件过采样的ADC(16位分辨率) 具

    2024年01月19日
    浏览(60)
  • 解决STM32G431输出PWM扫频消失问题

            最近练习蓝桥杯嵌入式的题目,需要输出一个PWM扫频的信号,遇到了PWM变频率时有几率消失的问题, 下面来研究下原因和解决方案。          由于Keil怎么改设置都不肯给我看外设寄存器,下面用CubeIDE复现下PWM消失的情况,用ST-Link调试。         时钟倍频

    2024年04月10日
    浏览(49)
  • 嵌入式常用术语与AHL-STM32L431运行示例

    1.1 与硬件相关的术语 1. 封装(Package) 2. 印制电路板(Printed Circuit Board,PCB) 3. 动态可读写随机存储器(Dynamic Random Access Memory,DRAM) 4. 静态可读写随机存储器(Static Random Access Memory,SRAM) 5. 只读存储器(Read Only Memory,ROM) 6. 闪存存储器(Flash Memory) 7. 模拟量(Analog Signal) 

    2024年04月14日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包