嵌入式蓝桥杯各模块的学习

这篇是基于我之前写的代码写的,可能会有点乱,本人也是最近开始学的stm32,希望可以对参加蓝桥杯的朋友提供一点帮助。如果有什么问题,请各位大佬及时指出改正,代码自取。

RCC模块

 

高速时钟(HSE)设置成晶体/陶瓷谐振器(Crystal/Ceramic Resonator)

SYS模块

Debug设置串行线(Serial Wire)

时钟源(Timebase Source)设置成SysTick

LED模块

led原理图

由原理图和74HC_HCT573手册可以看出,有一个PD2的锁存器,当PD2输出高电平,LE输入高电平即可使能锁存器,并且PC8-PC15输出低电平二极管导通。

Cubemx配置

PC8-PC15,PD2,都选择GPIO_Output

默认为低电平,因为锁存器高电平使能,那我们就低电平防止他初始化就打开。

led的话默认为高电平

led模块代码

user.c

void led_xs(unsigned char a){

HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_SET);

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All, GPIO_PIN_SET);

HAL_GPIO_WritePin(GPIOC,a<<8, GPIO_PIN_RESET);

HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_RESET);

}

main.c

u8 frq_ui = 0;

u8 adc_ui = 1;

u8 pwm_ui = 2;

u8 dac_ui = 3;

u8 ui = 0;

u8 die = 0;

u8 right= 1;

u8 left = 2;

u8 right_stream = 3;

u8 left_stream = 4;

u8 bring = 5;

u8 pwm_line = 0;

u8 dac_line = 0;

u16 reset_num = 0;

u16 reset_old = 0;

u8 led_flag = 0;

u32 led_tick=0;

u32 led_num = 0;

void led_proc(){

if(uwTick-led_tick<100){

return ;

}

led_tick = uwTick;

if(led_flag ==right){

led_num =0x55;

}else if(led_flag == left){

led_num = 0xaa;

}

else if(led_flag == right_stream){

led_num = led_num>>1;

if(led_num == 0||led_num ==(0x55>>1)||led_num ==0xaa||led_num==(0xff>>1)){

led_num = 0x80;

} }

else if(led_flag == left_stream){

led_num = led_num<<1;

if(led_num==0){

led_num = 0x01;

}

}

else if(led_flag == bring){

led_num = 0xff;

}else if(led_flag == die){

led_num = 0;

} if(ui!=frq_ui){

led_num=0;

}

led_xs(led_num);

}

led怎么说呢,做了好几届,我感觉用位操作是最稳妥的,异或也可以用跟位操作搭配使用,记得要在初始化给零让led全灭。,


KEY模块

Key原理图

4个按键都一样的,当按键没按下默认是跟Vdd接在一起,所以输入是高电平,当按键按下后,相当与接地,输入是高电平。然后这里的话应该是上拉。

  • 上拉配置:如果按键的输入端口配置了一个上拉电阻,那么当按键未被按下时,由于上拉电阻的作用,输入端口的电平将保持为高电平。当按键被按下时,按键通常会连接到低电平(如GND),从而拉低输入端口的电平状态。

  • 下拉配置:如果按键的输入端口配置了一个下拉电阻,那么当按键未被按下时,输入端口的电平将保持为低电平。然而,在实际应用中,按键电路很少使用下拉配置,因为当按键未被按下时,我们希望输入端口保持一个确定的电平状态(通常是高电平),以便于检测按键的按下动作。

Cubemx配置

PA0 PB0-PB2设置GPIO mode成输入,上拉。

Key模块代码

user.c

u8 key_down,key_old,key_up,key_value = 0;

void key_cz(){

if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==0){

key_value = 1;

}

else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==0){

key_value = 2;

}

else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)==0){

key_value = 3;

}

else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==0){

key_value = 4;

}

else

key_value = 0;

key_down = key_value&(key_value^key_old);

key_up = ~key_value&(key_value^key_old);

key_old = key_value;

}

main.c

u32 key_long_tick = 0;

u32 key_tick=0;

u8 key3_num = 0;

u8 key4_num = 0;

u32 double_tick=0;

RTC_TimeTypeDef T_start;

void key_proc(){

if(uwTick-key_tick<20){

return ;

}

key_tick = uwTick;

key_cz();

if(ui == frq_ui){

if(key_down==1){

ui = adc_ui;

LCD_Clear(Black);

}

else if(key_down==2){

if(led_flag!=right){

led_flag = right;

}else{

led_flag = left; }

}

else if(key_down==3){

if(led_flag!=right_stream){

led_flag = right_stream;

}else{

led_flag = left_stream;

} }

else if(key_down==4){

if(led_flag!=bring){

led_flag = bring;

}else{

led_flag = die;

}

}

}

else if(ui==adc_ui){

if(key_down==1){

ui = pwm_ui;

LCD_Clear(Black);

}

else if(key_down==2){

if(mcp_num!=127){

mcp_num = 127;

}else{

mcp_num = 0; }

}

else if(key_up==3){

mcp_num++;

if(mcp_num>127){

mcp_num = 127;

} }

else if(key_up==4){

mcp_num--;

if(mcp_num>127){

mcp_num = 0;

}

}

else if(key_down==3||key_down==4){

key_long_tick = uwTick;

}

if(key_value==3&&(uwTick-key_long_tick>800)){

mcp_num++;

if(mcp_num>127){

mcp_num = 127;

}

}

if(key_value==4&&(uwTick-key_long_tick>800)){

mcp_num--;

if(mcp_num>127){

mcp_num = 0;

}

}

}

else if(ui==pwm_ui){

if(key_down==1){

ui = dac_ui;

LCD_Clear(Black);

}

else if(key_down==2){

pwm_line++;

if(pwm_line>3){

pwm_line = 0;

}

}

else if(key_down==3){

if(pwm_line==0){

pa6_frq+=1000;

if(pa6_frq>10000){

pa6_frq = 10000;

}

}

if(pwm_line==1){

pa6_duty+=10;

if(pa6_duty>90){

pa6_duty = 90;

}

}

if(pwm_line==2){

pa7_frq+=1000;

if(pa7_frq>10000){

pa7_frq = 10000;

}

}

if(pwm_line==3){

pa7_duty+=10;

if(pa7_duty>90){

pa7_duty = 90;

}

}

}

else if(key_down==4){

if(pwm_line==0){

pa6_frq-=1000;

if(pa6_frq<=1000){

pa6_frq = 1000;

}

}

if(pwm_line==1){

pa6_duty-=10;

if(pa6_duty<10){

pa6_duty = 10;

}

}

if(pwm_line==2){

pa7_frq-=1000;

if(pa7_frq<1000){

pa7_frq = 1000;

}

}

if(pwm_line==3){

pa7_duty-=10;

if(pa7_duty<10){

pa7_duty = 10;

}

}

}

}

else if(ui==dac_ui){

if(key_down == 1){

ui = frq_ui;

LCD_Clear(Black);

reset_old = reset_num;

HAL_RTC_SetTime(&hrtc,&T_start,RTC_FORMAT_BIN);//rtc设置时间

EEP_write(0xff,reset_old);//在0xff这个块写入数据reset_old

EEP_write(11,T_start.Hours);

EEP_write(22,T_start.Hours);

EEP_write(33,T_start.Hours);

}

else if(key_down == 2){

dac_line ++;

if(dac_line>4)

dac_line = 0;

}

//长按

else if(key_down == 3 || key_down == 4){

key_long_tick = uwTick;

}

if(key_value == 3&&(uwTick-key_long_tick>800)){

DAC_key3(43,2,1,1,1);

}

if(key_value == 4&&(uwTick-key_long_tick>800)){

DAC_key4(50,2,1,1,1);

}

//双击

if(key_up == 3 && key3_num == 0){

key3_num=1;

double_tick = uwTick;

}

else if(key3_num==1){

if(key_up == 3 && uwTick-double_tick <300){

key3_num = 0;

DAC_key3(4095,255,24,60,60);

}else if(uwTick-double_tick > 300){

key3_num = 0;

DAC_key3(1,1,1,1,1);

}

}

if(key_up == 4 && key4_num == 0){

key4_num=1;

double_tick = uwTick;

}

else if(key4_num==1){

if(key_up == 4 && uwTick-double_tick < 300){

key4_num = 0;

DAC_key4(4095,255,24,60,60);

}else if(uwTick-double_tick > 300){

key4_num = 0;

DAC_key4(1,1,1,1,1);

}

}

}

}

按键的话最难的应该是长按和双击。这里先看看吧,我把源码放在下面自取。

//长按

else if(key_down == 3 || key_down == 4){

key_long_tick = uwTick;

}

if(key_value == 3&&(uwTick-key_long_tick>800)){

DAC_key3(43,2,1,1,1);

}

if(key_value == 4&&(uwTick-key_long_tick>800)){

DAC_key4(50,2,1,1,1);

}

长按的话,我都是用系统时钟的,定义一个u8 key_long_tick;这里是key_down==3||key_down==4分别指的是按键PB2和PA0按下的时候,当按键按下key_long_tick = uwTick;然后if(key_value == 3&&(uwTick-key_long_tick>800))这里的意思是判断是不是一直按着,并且这个时间是>800,那么可能有人要问了,为什么不能等于800,当然可以,只是我觉得不差这1ms,另一个PA0也是一样的。

//双击

if(key_up == 3 && key3_num == 0){

   key3_num=1;

   double_tick = uwTick;

   }

   else if(key3_num==1){

   if(key_up == 3 && uwTick-double_tick <300){

   key3_num = 0;

   DAC_key3(4095,255,24,60,60);

   }else if(uwTick-double_tick > 300){

   key3_num = 0;

   DAC_key3(1,1,1,1,1);

   }

}

双击的话,我以PB2为例子,定义两个变量u32 double_tick =0; u8 key_num3=0; if(key_up == 3 && key3_num == 0)这里就是说按键3按下并且key_num3=0;则 key3_num=1; double_tick = uwTick; 然后判断key_num3==1, if(key_up == 3 && uwTick-double_tick <300)再判断是否在300ms内再按一次一次按键,并且是小于300ms的。否则是单击。


LCD模块

Lcd原理图

这里我强烈建议用官方给的模板,不要自己创工程,因为官方给的模板是cubemx帮你配置好的

这个压缩包我也放在下面

Cubemx就不用配置了,但是要记得再keil里面加lcd.c的路径,还有把lcd.c加进工程里

添加lcd.c到工程里、

main.c

lcd代码粘贴出来有点问题,所以这里就截图把

代码比较多这里简单看一下478行和479行的代码,这个Line6就是指第六行,因为lcd屏的分辨率是320*240,这里320是列,16*1,16*2,这个是从右开始算,越小越靠右,T_start.Second/10+'0'这个意思是将数值转成字符,假定T_start.Second = 10,10/10=1+'0',0的ascii码是48,48+1=49,也就是字符1。T_start.Second%10+'0',跟前面的差不多%10就是取低位,10%10=0+'0',0+48=48,也就是字符0。

*函数名:LCD_DisplayChar

*描述:显示一个字符(16点宽,24点高)。

*输入:—行:显示字符形状的行。

*该参数可以是以下值之一:

  • Linex:其中x可以是0 ~ 9

*—Column:起始列地址。

*—Ascii:字符Ascii码,取值范围为0x20 ~ 0x7E。

*输出:无


RTC模块

Cubemx配置

时钟配置成32khz

Hour Format(小时格式) Hourformat 24

Asynchronous Predivider value(异步预分频器值) 32-1

Synchronous Predivider value(同步预分频器值) 1000-1

这里都-1是因为预分频器从0开始计数

我的理解是32000/32/1000 =1s

Rtc模块代码

user.c

RTC_DateTypeDef D;

RTC_TimeTypeDef T;

u32 rtc_tick = 0;

void RTC_proc(){

if(uwTick-rtc_tick<100){

return ;

}

rtc_tick = uwTick;

/* Get the RTC current Time */

HAL_RTC_GetTime(&hrtc, &T, RTC_FORMAT_BIN);

/* Get the RTC current Date */

HAL_RTC_GetDate(&hrtc, &D, RTC_FORMAT_BIN);

}

有一个注意的地方,就说HAL_RTC_GetTime必须要在HAL_RTC_GetDate的上面,不然会有问题。


定时器模块

1.输入捕获

信号发生器原理图

这里PA15接的是R40,PB4接的是R39,然后中间分别有个J10,J9的跳线帽,可以拔下来用于捕获pwm。

Cubemx配置

这里的PA15配置成TIM2_CH1,PB4配置成TIM3_CH1

这个是TIM2的配置,Clock Source配置内部时钟,预分配配置成80-1,NVIC勾上,我这里是两路捕获(同时捕获频率和占空比),如果只需要捕获频率,那就只需要一个通道就行了。后面也不用改上升沿捕获还是下降沿。我这里配置的是通道一上升沿,通道二下降沿。(TIM3也是一样的)

输入捕获的代码

user.c

uint F39,F40 = 0;

float D39,D40 = 0;

float ZT3,JT3 = 0;

float ZT2,JT2 = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef htim){

if(htim==&htim3){

if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1){

ZT3 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取通道1直接测量值

JT3 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);//获取通道2间接测量值占空比

__HAL_TIM_SetCounter(htim,0);

F39 = (1000000)/ZT3;//频率

* D39 = 100*(JT3/ZT3);

HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_1);

HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_2);

}

}

if(htim==&htim2){

if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1){

ZT2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取通道1直接测量值

JT2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);//获取通道2间接测量值占空比

__HAL_TIM_SetCounter(htim,0);

F40 = (1000000)/ZT2;//频率

D40 = 100*(JT2/ZT2);

HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_1);

HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_2);

}

}

}

频率的话,我这里是通道一直接捕获,通道二间接捕获,所以频率等于通道一直接捕获的值

 频率 = (80000000/80)/通道一捕获值

占空比 = 100*(通道二间接捕获值/通道一捕获值)

记得要在main函数初始化


2.pwm

我这里设置是PA6-TIM16_CH1,PA7-TIM17_CH1

这里TIM16的通道一选择成PWM输出,预分配是80-1,计数周期设置为1000-1,自动重装载开启。

pwm的代码

user.c


u16 pa6_frq = 1000;

u16 pa7_frq = 2000;

u8 pa6_duty = 50;

u8 pa7_duty = 50;

u32 pwm_tick = 0;

void PWM_proc(){

if(uwTick-pwm_tick<100){

return ;

}

pwm_tick = uwTick;

__HAL_TIM_SetAutoreload (&htim16,1e6/pa6_frq-1);//设置计数周期,1000000/1000 = 1000-1

__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,1e6/pa6_frq*pa6_duty/100);//1000*50/100 = 500

//占空比 = 500/1000 = 0.5 = 50%

__HAL_TIM_SetAutoreload (&htim17,1e6/pa7_frq-1);//设置计数周期,1000000/2000 = 500-1

__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,1e6/pa7_frq*pa7_duty/100);//500*50/100=250

//占空比 = 250/500 = 0.5 = 50%

}

pwm的话,以pa6为例子,定义一个u16 pa6_frq = 1000;默认一开始的频率设置为1000;u8 pa6_duty = 50;默认一开始的占空比设置为50

__HAL_TIM_SetAutoreload (&htim16,1e6/pa6_frq-1);

//设置计数周期,1000000/1000 = 1000-1

__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,1e6/pa6_frq*pa6_duty/100);

//1000*50/100 = 500

//占空比 = 500/1000 = 0.5 = 50%

pwm也是要记得在main函数初始化


3.内部中断

我这段代码是没有内部中断的,但是我也说一下,这里选的是TIM4,内部时钟,预分配80-1,计数器1000-1,自动重装载开启。这里应该是10ms中断一次,80*10000/80000000 = 0.01s=10ms

勾上NVCI

然后就是重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);这个中断回调函数,然后在里面判断是不是&htim4这个句柄,我这里就随便创建了一个讲解一下,int a = 0;每10ms进入一次回调函数运行a++。

记得要在main.c初始化

HAL_TIM_Base_Start_IT(&htim4);


I2C模块

i2c原理图

eeprom的话肯定得看手册,到时候官方会给i2c_hal.c,i2c_hal.h这两个文件,我们只需要看时序图然后写函数就可以了。

我们用到的是at24c02

这里前四位都已经写好了1010也就是16进制的a,后面的A2,A1,A0都是接地的,所以都是0,然后R/W,R是1,W是0。

写操作时序

读操作时序

i2c_hal.c

void EEP_write(uint8_t add,uint8_t data){

I2CStart();

I2CSendByte(0xa0);

I2CWaitAck();

I2CSendByte(add);

I2CWaitAck();

I2CSendByte(data);

I2CWaitAck();

I2CStop();

HAL_Delay(5);

}



uint8_t EEP_read(uint8_t add){

uint8_t data;

I2CStart();

I2CSendByte(0xa0);

I2CWaitAck();

I2CSendByte(add);

I2CWaitAck();

I2CStart();

I2CSendByte(0xa1);

I2CWaitAck();

data = I2CReceiveByte();

I2CSendNotAck();

I2CStop();

return data;

}

mcp的话,也是差不多的,要查手册。

如果是写的话,就是最后一位就是0,所以是01011110也就是0x5e,读的话就是0x5f。我这里是写

mcp写操作时序

i2c_hal.c

void MCP_write(uint8_t mcp){

I2CStart();

I2CSendByte(0x5e);

I2CWaitAck();

I2CSendByte(mcp);

I2CWaitAck();

I2CStop();

}

串口模块

串口原理图

Cubemx配置

选择异步,然后波特率比赛要看要求,我这里只是演示一下

NVIC勾上,如过要用dma就开启dma,我这里是nvic和dma都开启了

如果要开启dma的话,就点击dma setting->add->select选择要rx还是tx,还是两个都要,默认就可以了。

对了,如果觉得printf重定义的函数记不住的话,可以试一下我这个方法。在keil里找到help,选择第一个。

在搜索,输入fputc,然后点击第一个,复制上半部分,记住要鼠标右击复制,不要ctrl+c

然后再加上一行    HAL_UART_Transmit(&huart1,(u8 *)&ch,1, 50);就可以实现printf打印了

串口发送代码

user.c

#include <stdio.h>

struct __FILE

{

int handle;

/* Whatever you require here. If the only file you are using is /

* /* standard output using printf() for debugging, no file handling /

* /* is required. */

};

/* FILE is typedef’d in stdio.h. */

FILE __stdout;

int fputc(int ch, FILE *f)

{

HAL_UART_Transmit(&huart1,(u8 )&ch,1, 50);

* /* Your implementation of fputc(). */

return ch;

}

串口接收代码

user.c

_Bool ui_Mode;

u32 rx_tick = 0;

void RX_proc(){

if(uwTick-rx_tick<50){

return ;

}

rx_tick = uwTick;

if(rx_pointer==1 && rx_buf[0]=='#'){

LCD_Clear(Black);

if(ui_Mode!=1){

ui_Mode =1;

LCD_WriteReg(R96, 0xA700);

LCD_WriteReg(R1,0x0100);

}else{

ui_Mode = 0;

LCD_WriteReg(R96, 0x2700);

LCD_WriteReg(R1,0x0000);

}

printf("成功翻转屏幕\r\n");

}else if(rx_pointer>0){

printf("%s\r\n",rx_buf);

}

rx_pointer = 0;

memset(rx_buf,0,sizeof(rx_buf));

}


u8 rx_pointer,rx_data;

u8 rx_buf[30];

u8 tx_buf[30];

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

rx_tick = uwTick;

HAL_UART_Receive_DMA(&huart1,&rx_data,1);

rx_buf[rx_pointer++] = rx_data;

//HAL_UART_Receive_IT(&huart1,&rx_data, 1);

}

串口接收的话就定义三个变量,u8 rx_buf[30].rx_pointer,rx_data;然后在串口的中断回调函数接收,每来一个字符就给rx_data,然后rx_data赋值给rx_buf,然后记得接受完还有开启HAL_UART_Receive_IT(&huart1,&rx_data, 1);如果是要用dma,那就开启HAL_UART_Receive_DMA(&huart1,&rx_data,1);这里我其实有点疑问,好像不能把这个HAL_UART_Receive_DMA(&huart1,&rx_data,1);放在rx_buf[rx_pointer++] = rx_data;的后面,放在后面好像会有问题,放前面就没什么问题。

然后在定义一个RX_proc()函数用来处理接收到数据的操作

记得接收中断也要初始化,我这里用的是dma不用dma就把它注释掉,用旁边的串口接收中断。


ADC模块

模拟输入原理图

Cubemx配置

这里因为adc1是要用两个引脚,PB14和PB12,所以Number of Convesion配置成2,

然后有两个rank,rank1和rank2,分别对应的是PB14和PB12,先采集通道5然后再采集通道11的adc。这里的PB14采集的是MCP的adc值。

这个mcp的电压值好像是最大只能到3.0,你们可以去试试看。

adc代码

user.c

u8 mcp_num = 0;

u32 adc_tick = 0;

u32 r37_value = 0;

u32 r38_value = 0;

u32 mcp_value = 0;

float mcp_volt = 0;

float r37_volt = 0;

float r38_volt = 0;

void ADC_proc(){

if(uwTick-adc_tick<100){

return ;

}

adc_tick = uwTick;

HAL_ADC_Start(&hadc1);

r38_value = HAL_ADC_GetValue(&hadc1);

HAL_ADC_Start(&hadc1);

mcp_value = HAL_ADC_GetValue(&hadc1);

HAL_ADC_Start(&hadc2);

r37_value = HAL_ADC_GetValue(&hadc2);

r38_volt = r38_value3.3/4027.0;

* mcp_volt = mcp_value*3.3/4027.0;

r37_volt = r37_value*3.3/4027.0;

MCP_write(mcp_num);

}

最后,祝愿大家参加蓝桥杯嵌入式的朋友拿到一个好成绩!


  • 37
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值