这篇是基于我之前写的代码写的,可能会有点乱,本人也是最近开始学的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);
}
最后,祝愿大家参加蓝桥杯嵌入式的朋友拿到一个好成绩!