/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "math.h"
#include "font6x8.h"
#include "sdd1306.h"
#include "key.h"
#include "stdio.h"
#include "vofa.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
int a,b,c,d;
float f=0.5;float f1=1;float f2=2;float z=0;
float t=3.14/4;
int n =1;
uint16_t current_freq = 100;
uint16_t current_duty = 50;
#define K1 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11)
#define K2 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12)
#define BUFFER_SIZE 64
uint8_t rx_byte; // 当前接收到的字节
char rx_buffer[BUFFER_SIZE]; // 接收缓冲区
uint16_t rx_index = 0; // 当前写入位置
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void ProcessReceivedString(char *input);
static uint32_t key_last_time = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//中断回调
{
if (huart == &huart3) {
// 如果收到的是 '\n' 或 '\r',表示命令结束
if (rx_byte == '\n' || rx_byte == '\r') {
if (rx_index > 0) {
rx_buffer[rx_index] = '\0'; // 结束字符串
// 处理接收到的内容
ProcessReceivedString(rx_buffer);
// 清空缓冲区
rx_index = 0;
}
} else {
// 普通字符加入缓冲区(防溢出)
if (rx_index < BUFFER_SIZE - 1) {
rx_buffer[rx_index++] = rx_byte;
}
}
// 重新开启下一次接收
HAL_UART_Receive_IT(&huart3, &rx_byte, 1);
}
}
void ProcessReceivedString(char *input)
{
char output[128];
// 构造输出:"xx is ok !"
snprintf(output, sizeof(output), "%s is ok !", input);
// 发送回上位机
printf("Command received: %s\r\n", input);
// 如果有 OLED 屏幕,也在屏幕上显示
OLED_Clear();
OLED_ShowString(0, 16, output,1);
OLED_UpdateScreen();
}
static void BreathingLight(void){
float t1 = HAL_GetTick()*0.001;
float duty1 = 0.5 * sin(f*3.14*t1+a*t/f)+0.5;
uint16_t arr1 = __HAL_TIM_GET_AUTORELOAD(&htim1);
uint16_t ccr1 = duty1 * (arr1+1);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,ccr1);
float t2 = HAL_GetTick()*0.001;
float duty2 = 0.5 * sin(f*3.14*t2+b*t/f)+0.5;
uint16_t arr2 = __HAL_TIM_GET_AUTORELOAD(&htim2);
uint16_t ccr2 = duty2* (arr2+1);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,ccr2);
float t3 = HAL_GetTick()*0.001;
float duty3 = 0.5 * sin(f*3.14*t3+c*t/f)+0.5;
uint16_t arr3 = __HAL_TIM_GET_AUTORELOAD(&htim3);
uint16_t ccr3 = duty3 * (arr3+1);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr3);
float t4 = HAL_GetTick()*0.001;
float duty4 = 0.5 * sin(f*3.14*t4+d*t/f)+0.5;
uint16_t arr4 = __HAL_TIM_GET_AUTORELOAD(&htim4);
uint16_t ccr4 = duty4 * (arr4+1);
__HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_3,ccr4);
}
//static void UpdateOLEDDisplay(void)
//{
// uint32_t psc = htim1.Init.Prescaler;
// uint32_t arr = htim1.Init.Period;
//
// float timer_clock_freq = (float)HAL_RCC_GetPCLK2Freq();
// float freq = timer_clock_freq / ((arr + 1) * (psc + 1));
//
// // 3. 获取TIM1通道1的比较值(CCR)
// uint32_t ccr = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2);
//
// // 4. 计算占空比
// float duty_cycle = (float)ccr / (arr + 1) * 100.0f; // 转换为百分比
// char display_buf[32];
//
// // 3. 将数值格式化为字符串
// sprintf(display_buf, "Freq: %.1f Hz", freq);
// OLED_ShowString(0, 0, (uint8_t*)display_buf, 16); //
//
// sprintf(display_buf, "Duty: %.1f %%", duty_cycle);
// OLED_ShowString(0, 8, (uint8_t*)display_buf, 16); //
//
// OLED_DrawSquareWave(0, 32, 16, freq, duty_cycle);
//
// OLED_UpdateScreen(); // 刷新屏幕显示
//}
//void UpdateOLEDDisplay(void)
//{
// char buf[32];
// // 直接使用 current_freq 和 current_duty 显示
// sprintf(buf, "Freq: %lu Hz", (unsigned long)current_freq);
// OLED_ShowString(0, 0, buf, 1); // 第一行显示频率
// sprintf(buf, "Duty: %d %%", current_duty);
// OLED_ShowString(0, 8, buf, 1); // 第二行显示占空比
// // 绘制方波图形(x_start=0, y_center=32, height=16)
// OLED_DrawSquareWave(0, 32, 16, (float)current_freq, (float)current_duty);
// OLED_UpdateScreen(); // 统一刷新屏幕
//}
void UpdateOLEDDisplay(void)
{
char buf[32];
sprintf(buf, "Freq: %lu Hz", (unsigned long)current_freq);
OLED_ShowString(0, 0, buf, 1); // 显示频率
sprintf(buf, "Duty: %d %%", current_duty);
OLED_ShowString(0, 8, buf, 1); // 显示占空比
OLED_UpdateText();
// 不再绘制静态波形图
// 所有波形由 OLED_UpdateWaveform() 负责动态刷新
}
//重定向
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart3, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart3, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_I2C1_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
OLED_Init();
OLED_Clear();
// 初始化目标参数
OLED_SetWaveParams((float)current_freq, (float)current_duty);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
// 更新PWM
HAL_UART_Receive_IT(&huart3, &rx_byte, 1);//启动中断
Update_ARR(); // 设置ARR和CCR
OLED_Fill(0);
while (1)
{
Key_Scan_All();
// 每 50ms 更新一次文字显示
static uint32_t last_update = 0;
if (HAL_GetTick() - last_update >= 50)
{
UpdateOLEDDisplay();
last_update = HAL_GetTick();
}
// 动态刷新波形(约20Hz)
OLED_UpdateWaveform();
//老式呼吸灯
// if (K1 == 0)
// {
// HAL_Delay(10);
// if(K1 == 0)
// {
// while(K1==0);
// n = -n;
//
// }
// }
// if (K2 == 0)
//// {
//// HAL_Delay(10);
//// if(K2 == 0)
//// {
//// while(K2==0);
//// z=f2;
//// f2=f1;
//// f1=f;
//// f=z;
//// }
//// }
if (!K1 && (HAL_GetTick() - key_last_time) > 10)
{
key_last_time = HAL_GetTick();
n = -n;
while (!K1); // 等待释放(短时间可用)
}
if (!K2 && (HAL_GetTick() - key_last_time) > 10)
{
key_last_time = HAL_GetTick();
z=f2;
f2=f1;
f1=f;
f=z;
while (!K1); // 等待释放(短时间可用)
}
if (n == 1){
a=1;b=2;c=3;d=4;
BreathingLight();
}
else if(n == -1){
a=4;b=3;c=2;d=1;
BreathingLight();
}
// VOFA+ 数据发送(10Hz)
static uint32_t last_vofa_send = 0;
if (HAL_GetTick() - last_vofa_send >= 100)
{
Send_SquareWave_VOFA();
last_vofa_send = HAL_GetTick();
}
HAL_Delay(1); // 防止死循环占用过高
// //1.阻塞式发送
// HAL_UART_Transmit(&huart3,(uint8_t *)"hello world",12,0XFFFF);
// HAL_Delay(500);
// //2.重定向
// printf("hello world");
// HAL_Delay(500);
// //3.阻塞式接收
// uint8_t Buf[5];
// HAL_UART_Receive(&huart3,Buf,5,0XFFFF);
// HAL_UART_Transmit(&huart3,Buf,5,0XFFFF);
// HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
#include "key.h"
#include "main.h"
#include "tim.h"
extern uint16_t current_freq;
extern uint16_t current_duty;
// 按键消抖函数
static uint8_t key_Debounce(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) // 假设低电平有效
{
HAL_Delay(10);
if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET)
{
return 1; // 确认按下
}
}
return 0;
}
void Update_CCR(void)
{
uint32_t arr_val = __HAL_TIM_GET_AUTORELOAD(&htim1);
// CCR = ARR * (Duty / 100.0)
uint32_t CCR_new = (uint32_t)((float)(arr_val + 1) * (current_duty / 100.0f)) - 1;
// 确保 CCR 不会超出 ARR
if (CCR_new >= arr_val) CCR_new = arr_val - 1;
if (CCR_new < 1) CCR_new = 1;
// 使用 TIM1 Channel 2
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, CCR_new);
}
void Update_ARR(void)
{
uint32_t F_clk = HAL_RCC_GetPCLK2Freq();
uint32_t psc_val = htim1.Instance->PSC;
// 保护除以零
if (current_freq == 0) current_freq = FREQ_MIN;
uint32_t ARR_new = (F_clk / (current_freq * (psc_val + 1))) - 1;
// 确保 ARR 不会溢出或过小
if (ARR_new > 0xFFFF) ARR_new = 0xFFFF;
if (ARR_new < 1) ARR_new = 1;
__HAL_TIM_DISABLE(&htim1);
__HAL_TIM_SET_AUTORELOAD(&htim1, ARR_new);
__HAL_TIM_ENABLE(&htim1);
Update_CCR();
}
void Key_Scan_All(void)
{
// PINA2: 频率增加 (F+)
if (key_Debounce(GPIOA, GPIO_PIN_2) == 1)
{
if (current_freq < FREQ_MAX)
{
current_freq += FREQ_STEP;
if (current_freq > FREQ_MAX) current_freq = FREQ_MAX;
Update_ARR(); // 更新频率和 CCR
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET); // 等待按键释放
}
// PINA3: 频率减小 (F-)
if (key_Debounce(GPIOA, GPIO_PIN_3) == 1)
{
if (current_freq > FREQ_MIN)
{
current_freq -= FREQ_STEP;
if (current_freq < FREQ_MIN) current_freq = FREQ_MIN;
Update_ARR(); // 更新频率和 CCR
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_RESET);
}
// PINA4: 占空比增加 (D+)
if (key_Debounce(GPIOA, GPIO_PIN_4) == 1)
{
if (current_duty < DUTY_MAX)
{
current_duty += DUTY_STEP;
if (current_duty > DUTY_MAX) current_duty = DUTY_MAX;
Update_CCR(); // 更新占空比
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET);
}
// PINA5: 占空比减小 (D-)
if (key_Debounce(GPIOA, GPIO_PIN_5) == 1)
{
if (current_duty > DUTY_MIN)
{
current_duty -= DUTY_STEP;
if (current_duty < DUTY_MIN) current_duty = DUTY_MIN;
Update_CCR(); // 更新占空比
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET); // 等待按键释放
}
}
#include "i2c.h" // 包含hi2c1的声明
#include "sdd1306.h"
#include "font6x8.h"
#include <string.h>
#define OLED_I2C_ADDR 0x78 // OLED I2C写地址 (0x3C<<1)
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
static uint8_t OLED_Buffer[OLED_WIDTH * OLED_HEIGHT / 8];
//static uint8_t wave_x = 0; // 波形当前的X坐标
static uint32_t last_draw_time = 0; // 上次绘制波形的时间戳
static uint8_t wave_color = 1; // 波形颜色 (1 = 白色)
static float target_frequency = 1.0f; // 默认频率 1Hz
static float target_duty_cycle = 50.0f; // 默认占空比 50%
uint8_t OLED_GetPixel(int x, int y)
{
if (x < 0 || x >= OLED_WIDTH || y < 0 || y >= OLED_HEIGHT)
return 0;
uint8_t page = y / 8;
uint8_t bit = y % 8;
return (OLED_Buffer[page * OLED_WIDTH + x] >> bit) & 0x01;
}
void OLED_WriteCommand(uint8_t cmd) {
// 发送一个命令字节 (控制字节=0x00)
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00,
I2C_MEMADD_SIZE_8BIT, &cmd, 1, HAL_MAX_DELAY);
}
void OLED_WriteData(uint8_t data) {
// 发送一个数据字节 (控制字节=0x40)
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);
}
#include "font6x8.h"
const unsigned char font6x8[][6] = {
// ASCII 0x20 (空格) - 无像素
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x5F, 0x00, 0x00, 0x00}, // !
{0x00, 0x07, 0x00, 0x07, 0x00, 0x00}, // "
{0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00}, // #
{0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00}, // $
{0x23, 0x13, 0x08, 0x64, 0x62, 0x00}, // %
{0x36, 0x49, 0x55, 0x22, 0x50, 0x00}, // &
{0x00, 0x05, 0x03, 0x00, 0x00, 0x00}, // '
{0x00, 0x1C, 0x22, 0x41, 0x00, 0x00}, // (
{0x00, 0x41, 0x22, 0x1C, 0x00, 0x00}, // )
{0x14, 0x08, 0x3E, 0x08, 0x14, 0x00}, // *
{0x08, 0x08, 0x3E, 0x08, 0x08, 0x00}, // +
{0x00, 0x50, 0x30, 0x00, 0x00, 0x00}, // ,
{0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, // -
{0x00, 0x60, 0x60, 0x00, 0x00, 0x00}, // .
{0x20, 0x10, 0x08, 0x04, 0x02, 0x00}, // /
{0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00}, // 0
{0x00, 0x42, 0x7F, 0x40, 0x00, 0x00}, // 1
{0x42, 0x61, 0x51, 0x49, 0x46, 0x00}, // 2
{0x21, 0x41, 0x45, 0x4B, 0x31, 0x00}, // 3
{0x18, 0x14, 0x12, 0x7F, 0x10, 0x00}, // 4
{0x27, 0x45, 0x45, 0x45, 0x39, 0x00}, // 5
{0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00}, // 6
{0x01, 0x71, 0x09, 0x05, 0x03, 0x00}, // 7
{0x36, 0x49, 0x49, 0x49, 0x36, 0x00}, // 8
{0x06, 0x49, 0x49, 0x29, 0x1E, 0x00}, // 9
{0x00, 0x36, 0x36, 0x00, 0x00, 0x00}, // :
{0x00, 0x56, 0x36, 0x00, 0x00, 0x00}, // ;
{0x08, 0x14, 0x22, 0x41, 0x00, 0x00}, // <
{0x14, 0x14, 0x14, 0x14, 0x14, 0x00}, // =
{0x00, 0x41, 0x22, 0x14, 0x08, 0x00}, // >
{0x02, 0x01, 0x51, 0x09, 0x06, 0x00}, // ?
{0x32, 0x49, 0x59, 0x51, 0x3E, 0x00}, // @
{0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00}, // A
{0x7F, 0x49, 0x49, 0x49, 0x36, 0x00}, // B
{0x3E, 0x41, 0x41, 0x41, 0x22, 0x00}, // C
{0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00}, // D
{0x7F, 0x49, 0x49, 0x49, 0x41, 0x00}, // E
{0x7F, 0x09, 0x09, 0x09, 0x01, 0x00}, // F
{0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00}, // G
{0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00}, // H
{0x00, 0x41, 0x7F, 0x41, 0x00, 0x00}, // I
{0x20, 0x40, 0x41, 0x3F, 0x01, 0x00}, // J
{0x7F, 0x08, 0x14, 0x22, 0x41, 0x00}, // K
{0x7F, 0x40, 0x40, 0x40, 0x40, 0x00}, // L
{0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00}, // M
{0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00}, // N
{0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00}, // O
{0x7F, 0x09, 0x09, 0x09, 0x06, 0x00}, // P
{0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00}, // Q
{0x7F, 0x09, 0x19, 0x29, 0x46, 0x00}, // R
{0x46, 0x49, 0x49, 0x49, 0x31, 0x00}, // S
{0x01, 0x01, 0x7F, 0x01, 0x01, 0x00}, // T
{0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00}, // U
{0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00}, // V
{0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00}, // W
{0x63, 0x14, 0x08, 0x14, 0x63, 0x00}, // X
{0x07, 0x08, 0x70, 0x08, 0x07, 0x00}, // Y
{0x61, 0x51, 0x49, 0x45, 0x43, 0x00}, // Z
{0x00, 0x7F, 0x41, 0x41, 0x00, 0x00}, // [
{0x02, 0x04, 0x08, 0x10, 0x20, 0x00}, // backslash
{0x00, 0x41, 0x41, 0x7F, 0x00, 0x00}, // ]
{0x04, 0x02, 0x01, 0x02, 0x04, 0x00}, // ^
{0x80, 0x80, 0x80, 0x80, 0x80, 0x00}, // _
{0x00, 0x03, 0x07, 0x08, 0x00, 0x00}, // `
{0x20, 0x54, 0x54, 0x54, 0x78, 0x00}, // a
{0x7F, 0x48, 0x44, 0x44, 0x38, 0x00}, // b
{0x38, 0x44, 0x44, 0x44, 0x28, 0x00}, // c
{0x38, 0x44, 0x44, 0x48, 0x7F, 0x00}, // d
{0x38, 0x54, 0x54, 0x54, 0x18, 0x00}, // e
{0x08, 0x7E, 0x09, 0x01, 0x02, 0x00}, // f
{0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00}, // g
{0x7F, 0x08, 0x04, 0x04, 0x78, 0x00}, // h
{0x00, 0x44, 0x7D, 0x40, 0x00, 0x00}, // i
{0x40, 0x80, 0x84, 0x7D, 0x00, 0x00}, // j
{0x7F, 0x10, 0x28, 0x44, 0x00, 0x00}, // k
{0x00, 0x41, 0x7F, 0x40, 0x00, 0x00}, // l
{0x7C, 0x04, 0x18, 0x04, 0x78, 0x00}, // m
{0x7C, 0x04, 0x04, 0x04, 0x78, 0x00}, // n
{0x38, 0x44, 0x44, 0x44, 0x38, 0x00}, // o
{0xFC, 0x24, 0x24, 0x24, 0x18, 0x00}, // p
{0x18, 0x24, 0x24, 0x24, 0xFC, 0x00}, // q
{0x7C, 0x08, 0x04, 0x04, 0x08, 0x00}, // r
{0x48, 0x54, 0x54, 0x54, 0x24, 0x00}, // s
{0x04, 0x04, 0x3F, 0x44, 0x40, 0x00}, // t
{0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00}, // u
{0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00}, // v
{0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00}, // w
{0x44, 0x28, 0x10, 0x28, 0x44, 0x00}, // x
{0x1C, 0xA0, 0xA0, 0xA0, 0x7C, 0x00}, // y
{0x44, 0x64, 0x54, 0x4C, 0x44, 0x00}, // z
{0x00, 0x08, 0x36, 0x41, 0x00, 0x00}, // {
{0x00, 0x00, 0x77, 0x00, 0x00, 0x00}, // |
{0x00, 0x41, 0x36, 0x08, 0x00, 0x00}, // }
{0x02, 0x01, 0x02, 0x04, 0x02, 0x00} // ~
};
#include "vofa.h"
#include <stdio.h>
#include "main.h"
#include "math.h"
//void Send_SquareWave_VOFA(void)
//{
// float t = HAL_GetTick() * 0.001f;
// float freq = (float)current_freq;
// float duty_cycle = (float)current_duty / 100.0f;
// // 计算当前相位
// float phase = fmodf(t * freq, 1.0f);
// float pwm_val = (phase < duty_cycle) ? 1.0f : 0.0f;
// // 发送给VOFA+
// printf("%.3f\n", pwm_val); // 单通道模式下只需输出一个值
//}
void Send_SquareWave_VOFA(void)
{
float t = HAL_GetTick() * 0.001f; // 当前时间(秒)
float freq = (float)current_freq; // 频率(Hz)
float duty_cycle = (float)current_duty / 100.0f; // 占空比(0~1)
// 计算当前相位并生成 PWM 信号
float phase = fmodf(t * freq, 1.0f);
float pwm_val = (phase < duty_cycle) ? 1.0f : 0.0f;
// 同时发送 PWM 波形、频率、占空比(三通道)
printf("<raw>%.3f,%.3f,%.3f\n", pwm_val, freq, duty_cycle);
}
换成静态可调节方波
void OLED_UpdateScreen(void) {
// 刷新OLED显示
for (uint8_t page = 0; page < 8; page++) {
OLED_WriteCommand(0xB0 + page); // 设置页地址(0xB0 = page0)
OLED_WriteCommand(0x00); // 设置列低4位为0
OLED_WriteCommand(0x10); // 设置列高4位为0 (列地址0)
// 按当前页,将该页的缓冲区数据写出128字节
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT,
&OLED_Buffer[page * OLED_WIDTH], OLED_WIDTH, HAL_MAX_DELAY);
}
}
void OLED_Clear(void) {
memset(OLED_Buffer, 0x00, sizeof(OLED_Buffer)); // 缓冲设0
// 刷新OLED显示
for (uint8_t page = 0; page < 8; page++) {
OLED_WriteCommand(0xB0 + page); // 设置页地址(0xB0 = page0)
OLED_WriteCommand(0x00); // 设置列低4位为0
OLED_WriteCommand(0x10); // 设置列高4位为0 (列地址0)
// 按当前页,将该页的缓冲区数据写出128字节
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT,
&OLED_Buffer[page * OLED_WIDTH], OLED_WIDTH, HAL_MAX_DELAY);
}
}
void OLED_Fill(uint8_t color) {
// color=1 -> 全亮; color=0 -> 全黑
uint8_t fill = (color ? 0xFF : 0x00);
memset(OLED_Buffer, fill, sizeof(OLED_Buffer));
// 刷新显示(与OLED_Clear类似)
for (uint8_t page = 0; page < 8; page++) {
OLED_WriteCommand(0xB0 + page);
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x10);
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT,
&OLED_Buffer[page * OLED_WIDTH], OLED_WIDTH, HAL_MAX_DELAY);
}
}
void OLED_Init(void) {
HAL_Delay(100); // 上电延时,确保OLED电源稳定
OLED_WriteCommand(0xAE); // 1. 显示关闭
OLED_WriteCommand(0xD5); // 2. 设置显示时钟分频/振荡频率
OLED_WriteCommand(0x80); // 分频因子&振荡频率设置,0x80默认
OLED_WriteCommand(0xA8); // 3. 设置多路复用比
OLED_WriteCommand(0x3F); // 64行 (0x3F)
OLED_WriteCommand(0xD3); // 4. 设置显示偏移
OLED_WriteCommand(0x00); // 无偏移
OLED_WriteCommand(0x40); // 5. 设置显示开始行 (0)
OLED_WriteCommand(0x8D); // 6. 电荷泵设置
OLED_WriteCommand(0x14); // 开启电荷泵
OLED_WriteCommand(0x20); // 7. 内存地址模式
OLED_WriteCommand(0x02); // 页地址模式 (Page Mode)
OLED_WriteCommand(0xA1); // 8. 列地址重映射 (A0->A1 左右翻转)
OLED_WriteCommand(0xC8); // 9. 行扫描方向翻转 (上下翻转)
OLED_WriteCommand(0xDA); // 10. COM 引脚配置
OLED_WriteCommand(0x12); // COM配置 (0x12 for 64行)
OLED_WriteCommand(0x81); // 11. 对比度设置
OLED_WriteCommand(0x7F); // 对比度值 (0x7F)
OLED_WriteCommand(0xD9); // 12. 预充电周期
OLED_WriteCommand(0xF1); // 设置为 0xF1
OLED_WriteCommand(0xDB); // 13. 设置 VCOMH 电平
OLED_WriteCommand(0x40); // 0x40 默认
OLED_WriteCommand(0xA4); // 14. 取消全显示,按照RAM内容显示
OLED_WriteCommand(0xA6); // 15. 正常显示 (非反相)
OLED_WriteCommand(0xAF); // 16. 开启显示
}
//OLED_DrawPixel(x, y, color) : 绘制单个像素到缓冲区
void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
if (x >= OLED_WIDTH || y >= OLED_HEIGHT)
return; // 越界保护
uint16_t index = (y / 8) * OLED_WIDTH + x;
uint8_t bit = y % 8;
if (color)
OLED_Buffer[index] |= (1 << bit); // 置位
else
OLED_Buffer[index] &= ~(1 << bit); // 清位
}
void OLED_DrawChar(uint8_t x, uint8_t y, char chr, uint8_t color) {
if (chr < 0x20 || chr > 0x7E)
chr = '?'; // 非可显示字符用 '?' 代替
uint8_t index = chr - 0x20;
// 每个字符宽6
for (uint8_t col = 0; col < 6; col++) {
uint8_t lineBits = font6x8[index][col];
// lineBits的每个位对应该列的像素,从字模取出
for (uint8_t bit = 0; bit < 8; bit++) {
uint8_t pixelColor = (lineBits >> bit) & 0x1; // 取该位
if (!color) pixelColor = !pixelColor; // 如果color=0黑底白字,可取反
OLED_DrawPixel(x + col, y + bit, pixelColor);
}
}
}
//OLED_ShowString(x, y, *str, font, color) : 从指定位置开始显示字符串。
void OLED_ShowString(uint8_t x, uint8_t y, const char *str, uint8_t color) {
while (*str) {
OLED_DrawChar(x, y, *str, color);
x += 6; // 移动光标6列(字体宽度)
if (x + 6 > OLED_WIDTH) {
x = 0; // 换行到头
y += 8; // 下移一页(8像素高)
}
if (y >= OLED_HEIGHT) {
break; // 超出屏幕则退出
}
str++;
}
}
//画线
void OLED_DrawLine(int x0, int y0, int x1, int y1, uint8_t color) {
if (x0 == x1 && y0 == y1) {
OLED_DrawPixel(x0, y0, color);
return;
}
int dx = (x1 > x0 ? x1 - x0 : x0 - x1);
int dy = (y1 > y0 ? y0 - y1 : y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy; // 注意dy已取负
while (1) {
OLED_DrawPixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
}
if (e2 <= dx) {
err += dx;
y0 += sy;
}
}
}
//画矩形
void OLED_DrawRectangle(int x, int y, int width, int height, uint8_t color) {
// 绘制矩形的四条边
OLED_DrawLine(x, y, x + width - 1, y, color); // 上边
OLED_DrawLine(x, y + height - 1, x + width - 1, y + height - 1, color); // 下边
OLED_DrawLine(x, y, x, y + height - 1, color); // 左边
OLED_DrawLine(x + width - 1, y, x + width - 1, y + height - 1, color); // 右边
}
//画圆
void OLED_DrawCircle(int x0, int y0, int r, uint8_t color) {
int a = 0;
int b = r;
int d = 1 - r; // 判定参数
while (a <= b) {
// 八个对称点
OLED_DrawPixel(x0 + a, y0 + b, color);
OLED_DrawPixel(x0 - a, y0 + b, color);
OLED_DrawPixel(x0 + a, y0 - b, color);
OLED_DrawPixel(x0 - a, y0 - b, color);
OLED_DrawPixel(x0 + b, y0 + a, color);
OLED_DrawPixel(x0 - b, y0 + a, color);
OLED_DrawPixel(x0 + b, y0 - a, color);
OLED_DrawPixel(x0 - b, y0 - a, color);
a++;
if (d < 0) {
d += 2 * a + 1;
} else {
b--;
d += 2 * (a - b) + 1;
}
}
}
//void OLED_DrawSquareWave(int x_start, int y_center, int height, float frequency, float duty_cycle)
//{
// for(int page = 4; page < 8; page++)
// {
// memset(&OLED_Buffer[page * OLED_WIDTH], 0x00, OLED_WIDTH);
// }
// if (frequency <= 0.0f) return;
// if (duty_cycle <= 0.0f) duty_cycle = 0.0f; // 边界保护
// if (duty_cycle >= 100.0f) duty_cycle = 100.0f; // 边界保护
// int y_high = y_center - height / 2; // 高电平 Y 坐标
// int y_low = y_center + height / 2; // 低电平 Y 坐标
// int x_end = OLED_WIDTH; // 绘制到屏幕右侧
// int width = x_end - x_start; // 绘制区域总宽度
// int num_cycles = 2;
//
// int cycle_width = width / num_cycles;
// int high_width = (int)((float)cycle_width * (duty_cycle / 100.0f));
// int low_width = cycle_width - high_width;
//
// if (duty_cycle > 0.0f && high_width == 0) high_width = 1;
// if (duty_cycle < 100.0f && low_width == 0) low_width = 1;
//
// int current_x = x_start;
// for (int cycle = 0; cycle < num_cycles; cycle++)
// {
// int x_start_cycle = current_x;
//
// OLED_DrawLine(current_x, y_high, current_x + high_width - 1, y_high, wave_color);
// current_x += high_width;
//
// if (duty_cycle > 0.0f && duty_cycle < 100.0f) {
// OLED_DrawLine(current_x - 1, y_high, current_x - 1, y_low, wave_color);
// }
// OLED_DrawLine(current_x, y_low, current_x + low_width - 1, y_low, wave_color);
// current_x += low_width;
//
// if (duty_cycle > 0.0f && duty_cycle < 100.0f) {
// OLED_DrawLine(current_x - 1, y_low, current_x - 1, y_high, wave_color);
// }
//
// if (current_x >= x_end) break;
// }
//}
//void OLED_UpdateWaveform(void)
//{
// static uint32_t last_draw_time = 0;
// uint32_t now = HAL_GetTick();
// // 控制刷新率:~50Hz
// if (now - last_draw_time < 20) return;
// last_draw_time = now;
// float t = now / 1000.0f; // 时间(秒)
// float freq = (float)current_freq;
// float duty = (float)current_duty / 100.0f;
// // 计算当前应显示的电平状态
// float phase = fmodf(t * freq, 1.0f);
// uint8_t is_high = (phase < duty) ? 1 : 0;
// // 波形显示区域(垂直方向)
// const int y_center = 48;
// const int height = 16;
// const int y_top = y_center - height / 2; // y=40
// const int y_bottom = y_center + height / 2; // y=56
// // === 步骤1:整体左移一列(关键!避免“方块”)===
// for (int y = y_top; y <= y_bottom; y++)
// {
// for (int x = 0; x < OLED_WIDTH - 1; x++)
// {
// uint8_t pixel = OLED_GetPixel(x + 1, y);
// OLED_DrawPixel(x, y, pixel); // 把右边的像素移到左边
// }
// }
// // === 步骤2:在最右侧绘制新点(当前时刻电平)===
// for (int y = y_top; y <= y_bottom; y++)
// {
// OLED_DrawPixel(OLED_WIDTH - 1, y, is_high ? 1 : 0);
// }
// // === 步骤3:仅刷新受影响的页面(page 5~7 对应 y=40~63)===
// for (uint8_t page = 5; page <= 7; page++)
// {
// OLED_WriteCommand(0xB0 + page); // 设置页地址
// OLED_WriteCommand(0x00); // 低列地址
// OLED_WriteCommand(0x10); // 高列地址
// HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
// I2C_MEMADD_SIZE_8BIT,
// &OLED_Buffer[page * OLED_WIDTH],
// OLED_WIDTH, HAL_MAX_DELAY);
// }
//}
//
void OLED_SetWaveParams(float freq, float duty_cycle)
{
target_frequency = freq;
target_duty_cycle = duty_cycle;
}
void OLED_UpdateText(void)
{
for (uint8_t page = 0; page < 4; page++) // page 0~3 对应 y=0~31
{
OLED_WriteCommand(0xB0 + page); // 设置页地址
OLED_WriteCommand(0x00); // 低列地址
OLED_WriteCommand(0x10); // 高列地址
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
I2C_MEMADD_SIZE_8BIT,
&OLED_Buffer[page * OLED_WIDTH],
OLED_WIDTH, HAL_MAX_DELAY);
}
}
void OLED_UpdateWaveform(void)
{
static uint32_t last_draw_time = 0;
uint32_t now = HAL_GetTick();
// 控制刷新率为 10 FPS(每 100ms 一帧)
if (now - last_draw_time < 100)
return;
last_draw_time = now;
const int y_center = 48;
const int height = 16;
const int y_top = y_center - height / 2; // y=40
const int y_bottom = y_center + height / 2; // y=56
float total_duration = 12.8f; // 总时间跨度 12.8 秒
float dt_per_pixel = total_duration / 128.0f; // 每列 ≈0.1秒
for (int x = 0; x < OLED_WIDTH; x++)
{
// 计算这一列对应的时间点(从现在往回推)
float t_back = now / 1000.0f - (OLED_WIDTH - 1 - x) * dt_per_pixel;
float phase = fmodf(t_back * current_freq, 1.0f);
uint8_t is_high = (phase < (current_duty / 100.0f)) ? 1 : 0;
// 绘制该列:高电平区域点亮
for (int y = y_top; y <= y_bottom; y++)
{
OLED_DrawPixel(x, y, is_high);
}
}
// === 只刷新受影响的页面(page 5~7)===
for (uint8_t page = 5; page <= 7; page++)
{
OLED_WriteCommand(0xB0 + page); // 设置页地址
OLED_WriteCommand(0x00); // 列低4位
OLED_WriteCommand(0x10); // 列高4位
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
I2C_MEMADD_SIZE_8BIT,
&OLED_Buffer[page * OLED_WIDTH],
OLED_WIDTH, HAL_MAX_DELAY);
}
}