开发板
国信长天 CT117E-M4 开发板,stm32G431RBT6 芯片
外部晶振 24MHz
CubeMX配置工程
选芯片 STM32G431RBT6
LQFP封装
高速时钟选择外部晶振
配置时钟树(系统时钟随心配吧,不一定要非要80M)
串口Debug
写项目名,改工具链
选择固件版本及路径(自己看着改改)
固件路径改到对应目录就行(下图是某届蓝桥杯给的固件)
勾选生成外设初始化文件
最后Generate Code即可
Keil5配置
点击小魔术棒,选择调试器为CMSIS-DAP Debugger
,再点setting
,在 Flash Download 中选择Reset and Run
在项目目录里新建个文件夹,随便叫啥,比如bsp
(板载支持外设)
在项目中新建一个组
也改个名
每次新建文件可以放到dsp
里,然后向组里添加现有文件,只添加.c
即可
外设
LED 和 锁存器
LED管脚和LCD屏幕有存在GPIO复用,SN74HC573ADWR锁存器 可以隔离信号,防止两个外设干扰。LE管脚(PD2)拉高后锁存器导通,拉低后不导通。
LCD 通过锁存器和 PC8~15 引脚相连,低电平LED导通发光,因此上电初始化时,应默认输出高电平,在CubeMx终设置如下。
keil 中,点亮LED的代码如下:
1 2 3 4 5 6
| void LED_Disp(uint16_t dsLED){ HAL_GPIO_WritePin(GPIOC, 0xFF00, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOC, dsLED<<8, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); }
|
LCD液晶屏
官方提供 LCD 库,只需要无脑使能引脚output即可
有两个.h
文件和一个.c
文件,提供如下 User 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void LCD_SetTextColor(vu16 Color); void LCD_SetBackColor(vu16 Color); void LCD_ClearLine(u8 Line); void LCD_Clear(u16 Color); void LCD_SetCursor(u8 Xpos, u16 Ypos); void LCD_DrawChar(u8 Xpos, u16 Ypos, uc16 *c); void LCD_DisplayChar(u8 Line, u16 Column, u8 Ascii); void LCD_DisplayStringLine(u8 Line, u8 *ptr); void LCD_SetDisplayWindow(u8 Xpos, u16 Ypos, u8 Height, u16 Width); void LCD_WindowModeDisable(void); void LCD_DrawLine(u8 Xpos, u16 Ypos, u16 Length, u8 Direction); void LCD_DrawRect(u8 Xpos, u16 Ypos, u8 Height, u16 Width); void LCD_DrawCircle(u8 Xpos, u16 Ypos, u16 Radius); void LCD_DrawMonoPict(uc32 *Pict); void LCD_WriteBMP(u32 BmpAddress); void LCD_DrawBMP(u32 BmpAddress); void LCD_DrawPicture(const u8 *picture);
|
按键
按键按下时为低电平
启用定时器按键消抖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| typedef enum{ Key_Up=0, Key_Downing, Key_Down, Key_Uping, Key_Default, }BTN_State;
typedef enum{ BTN_release=0, BTN_shortPress, BTN_longPress, }BTN_Signal;
extern BTN_Signal btn_signal[4];
|
开两个定时器(10ms 消抖, 100ms判断短按、长按)实现长按、短按功能
设置好 PSC 和 ARR
开启中断
代码里记得在tim.c
里开启中断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim){ static uint8_t led_num = 0; static BTN_State btn_state[4] = {Key_Up, Key_Up, Key_Up, Key_Up}; static GPIO_TypeDef* BTN_GPIO_Port[4] = {BTN1_GPIO_Port, BTN2_GPIO_Port, BTN3_GPIO_Port, BTN4_GPIO_Port}; static uint16_t BTN_Pin[4] = {BTN1_Pin, BTN2_Pin, BTN3_Pin, BTN4_Pin}; if( htim->Instance == TIM6 ){ for(uint8_t btn = 0; btn < 4; btn++){ if( HAL_GPIO_ReadPin(BTN_GPIO_Port[btn], BTN_Pin[btn]) == GPIO_PIN_RESET ){ if( btn_state[btn] == Key_Up ){ btn_state[btn] = Key_Downing; }else if( btn_state[btn] == Key_Downing ){ btn_state[btn] = Key_Down; }else if( btn_state[btn] == Key_Uping ){ btn_state[btn] = Key_Down; } } if( HAL_GPIO_ReadPin(BTN_GPIO_Port[btn], BTN_Pin[btn]) == GPIO_PIN_SET ){ if( btn_state[btn] == Key_Down ){ btn_state[btn] = Key_Uping; }else if( btn_state[btn] == Key_Uping ){ btn_state[btn] = Key_Up; }else if( btn_state[btn] == Key_Downing ){ btn_state[btn] = Key_Up; } } } }else if( htim->Instance == TIM7 ){ static BTN_State btn_last_state[4] = {Key_Default, Key_Default, Key_Default, Key_Default}; static uint8_t BTN_Pressed_Time[4] = {0}; static bool level_down[4] = {false, false, false, false}; for(uint8_t i = 0; i < 4; i++ ){ if( btn_state[i] == Key_Down && btn_state[i] != btn_last_state[i] ){ BTN_Pressed_Time[i] = 0; level_down[i] = true; } if( level_down[i] ){ BTN_Pressed_Time[i]++; } if( BTN_Pressed_Time[i] > 5 ){ BTN_Pressed_Time[i] = 0; level_down[i] = false; btn_signal[i] = BTN_longPress; }else{ if( level_down[i] && btn_state[i] == Key_Up && btn_state[i] != btn_last_state[i] ){ level_down[i] = false; BTN_Pressed_Time[i] = 0; btn_signal[i] = BTN_shortPress; } } btn_last_state[i] = btn_state[i]; } } }
|
在主循环中处理按键信号,使用完记得清空!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void BTN_press_func(){ for(uint8_t i = 0; i < 4; i++){ if( btn_signal[i] == BTN_shortPress ){ switch (i){ case 0: break; case 1: break; case 2: break; case 3: break; default: break; } btn_signal[i] = BTN_release; }else if (btn_signal[i] == BTN_longPress){ switch (i){ case 0: break; case 1: break; case 2: break; case 3: default: break; } btn_signal[i] = BTN_release; } } }
|
PWM输出
使用定时器的通道输出PWM波,调节CCR寄存器改变占空比。
选定时器和通道,不能选 TIMx_CHxN,因为这是输出互补PWM波的
使能定时器,选择PWM生成
设置 PSC 和 ARR ,为了方便调整占空比,ARR尽量设置为 100-1,1000-1,…
代码里记得开启PWM生成
读取和修改占空比
1 2
| __HAL_TIM_GetCompare(&htim16, TIM_CHANNEL_1); __HAL_TIM_SetCompare(&htim16, TIM_CHANNEL_1, val);
|
定时器输入捕获
用来读取信号发生器产生信号的频率和占空比
信号发生器可以产生频率可调,占空比恒定的信号。通过跳线连接到 PA15 (TIM2_CH1)和 PB4 (TIM3_CH1)引脚。
配置定时器,开启内部时钟源,通道 1 输入捕获直接模式,通道 2 输入捕获间接模式(如果不测占空比的话可以不开CH2)
调节PSC,不调节ARR(默认最大)
开启中断
通道 1 检测上升沿,通道 2 检测下降沿,输入滤波器适当调整(反正0、8都能跑)
代码里开启两个通道的输入捕获中断
重写void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
函数
1 2 3 4 5 6 7 8 9 10 11
| void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){ if( htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){ ccr1_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1 ); ccr2_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2 ); __HAL_TIM_SetCounter(htim, 0); frq1 = (80000000 / 80) / ccr1_val1; duty1 = (double)ccr2_val1*100 / ccr1_val1; HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); } }
|
ADC
采集模拟电压
引脚 PB15 和 PB12 分别对应 ADC1_IN11 和 ADC2_IN15
设置单端采样
开启数据复写(可选)
读取ADC并转换为电压值
1 2 3 4 5
| double getVoltage(ADC_HandleTypeDef* adc_handle){ HAL_ADC_Start(adc_handle); uint16_t adc_value = HAL_ADC_GetValue(adc_handle); HAL_ADC_Stop(adc_handle); return adc_value * 3.3 / (1<<12);
|
IIC-eeprom
官方提供 IIC 的库,使用时只需编写简单的读写函数
同样无脑使能 PB6、PB7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| uint8_t eeprom_read(uint8_t addr){ uint8_t msg; I2CStart(); I2CSendByte(0xA0); I2CWaitAck(); I2CSendByte(addr); I2CWaitAck(); I2CStop(); I2CStart(); I2CSendByte(0xA1); I2CWaitAck(); msg = I2CReceiveByte(); I2CSendAck(); I2CStop(); return msg; }
void eeprom_write(uint8_t addr, uint8_t data){ I2CStart(); I2CSendByte(0xA0); I2CWaitAck(); I2CSendByte(addr); I2CWaitAck(); I2CSendByte(data); I2CWaitAck(); I2CStop(); }
|
IIC-数字电位计
可以调节 2^7 = 128 ( 0 ~ 127 )个挡位,输入数据一共 7 比特有效位数,输入数据越大,电阻越大,最大100k
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| uint8_t MCP4017_read(void){ uint8_t val; I2CStart(); I2CSendByte(0x5f); I2CWaitAck(); val=I2CReceiveByte(); I2CSendNotAck(); I2CStop(); return val; }
void MCP4017_write(uint8_t val){ I2CStart(); I2CSendByte(0x5e); I2CWaitAck(); I2CSendByte(val); I2CWaitAck(); I2CStop(); }
|
可通过 PB14(ADC1_IN5)读取其模拟电压值
ADC1此前已用过 IN11 ,因此使用最简单也最方便的多路ADC采样方法。
禁用连续转换、非连续转换、DMA转换模式,转换次数为1
每次读取时,重新设置ADC采样通道,一定要有HAL_ADC_PollForConversion(adc_handle, HAL_MAX_DELAY);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| double getVoltage(ADC_HandleTypeDef* adc_handle, uint32_t Channel){ if( adc_handle->Instance == ADC1 ){ ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = Channel; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5; if (HAL_ADC_ConfigChannel(adc_handle, &sConfig) != HAL_OK) { Error_Handler(); } } HAL_ADC_Start(adc_handle); if( adc_handle->Instance == ADC1 ){ HAL_ADC_PollForConversion(adc_handle, HAL_MAX_DELAY); } uint16_t adc_value = HAL_ADC_GetValue(adc_handle); HAL_ADC_Stop(adc_handle); return adc_value * 3.3 / (1<<12); }
|
UART串口
使能串口 USART1
异步模式
使能中断
波特率
串口阻塞发送
1
| HAL_UART_Transmit(&huart1, (u8*)rxmsg, strlen(rxmsg), 50);
|
串口非阻塞接收,重写函数void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| char rxmsg[30]; uint8_t rxloc=0; uint8_t rxdata; void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart){ rxmsg[rxloc++] = rxdata; rxmsg[rxloc] = 0; HAL_UART_Receive_IT(huart, &rxdata, 1); }
void uart_rx_process(){ if( rxloc != 0 ){ uint8_t temp = rxloc; HAL_Delay(1); if( temp == rxloc ){ ... rxloc = 0; } } }
|