PID温控实验平台搭建
文章目录
前言
最近,我突发奇想去翻阅了一些我本科期间所做的一些小项目,发现都挺有意思的!当年做这些项目的时候可走了很多弯路,所以想着可以将它们上传到网络上,并通过我的讲解文章可以帮助你们少走一些弯路!
今天,我要分享的是一个PID温控实验平台的搭建,因为我想要讲的详细一点,所以打算做成一个系列,手把手地教你学习和认识PID算法,了解这种算法在温度控制中的应用。由于我知识有限,如果出现一些错误,希望大家可以帮助我指出来,我们一起学习进步!!!
一、主程序功能描述
主函数的运行过程:
在开始温控之前,会先进行一段时间的软件配置,软件配置成功后。当DS18B20温度传感器检测到温度低于起始温度(30°C)时,将会开始加热到起始温度(30°C);当高于起始温度(30°C)时,将会冷却等待,直到温度达到起始温度(30°C);而温度变化数据将会实时显示在OLED屏幕上,让我们可以实时观察到控温趋势,并且通过串口发送及VOFA++上位机显示,实验数据曲线可以实时显示在电脑屏幕上,最终便于我们总结规律,得出结论。
VOFA++上位机
这是一款最直观、灵活、强大的插件驱动高自由度的上位机,我们通过特定的数据格式,来获得实时的数据曲线,便于我们直观地总结规律,归纳结论,并且VOFA++自由度很高,可以定制化数据的可视化方式,从而让我们可以更便捷地整定PID参数!
图1 VOFA++上位机
流水灯含义解释:
1)红灯闪烁代表正在进行配置所有软件;
2)绿灯闪烁代表加热棒正在工作,PID控制正在进行;
3)黄灯闪烁代表正在加热到起始温度(30°C);
4)蓝灯闪烁代表正在冷却到起始温度(30°C);
5)青灯亮起代表马上进入PID温控;
(主程序代码)
/******************main.c***********************/
// 温度
float T=0.0;
// 媒介变量
int i = 0;
// 定义PID结构体并初始化
PID pid;
// 一次PID调节的时间
#define WAIT_TIME 20
// 采样时间
#define SAMPLE_TIME 200
// 最大输出
#define MAX_OUT 10000
// 数组的元素的个数
#define ARR_NUM 4
// PID参数
const float Kp = 90;
const float Ki = 0.15;
const float Kd[ARR_NUM] = {1000,2000,3000,4000};
// 声明
void All_Soft_Config(void);
void Wait_Temperature_Init(void);
// 可以循环测试PID参数(1、参数改宏改;2、PID初始化改)
int main()
{
// 所有软件配置
All_Soft_Config();
for(uint8_t n=0;n<ARR_NUM;n++)
{
// i值清0
i = 0;
// 屏幕清空
OLED_Fill(0x00);
// 等待温度到达30度
Wait_Temperature_Init();
// 清空PID
PID_Clear(&pid);
// PID初始化
PID_Init(&pid,Kp,Ki,Kd[n],10000,MAX_OUT);
// 绿灯亮
LED_GREEN;
while(1)
{
// 绿灯闪烁
LED2_TOGGLE;
/* 测温 */
T = DS18B20_Update_Temperature(); // DS18B20更新温度
OLED_display_DS18B20_line(i,T); // 实时显示温度线
/* 根据PID计算值调整脉冲 */
PID_SingleCalc(&pid, 70, T); // 单级PID计算
Pulse_Wave = pid.output; // 改变其脉冲宽度
i++;
for(uint8_t tt = 0;tt<SAMPLE_TIME/200;tt++)
{
T = DS18B20_Update_Temperature(); // DS18B20更新温度
OLED_display_DS18B20_line(i,T); // 实时显示温度线
// 打印出波形
printf("Temperature-Pid: %.4f,%d,%d,%d,%.1f,%.3f,%.2f,%d\n",T,70,Pulse_Wave,i*SAMPLE_TIME/1000,pid.kp,pid.ki,pid.kd,SAMPLE_TIME);
SysTick_Delay_ms(200);
}
// 若超出时间或者达到了特定温度直接换一个参数
if(T>=110 || i >=(WAIT_TIME*60)*1000/SAMPLE_TIME)
{
for(uint8_t zero_time=0;zero_time<5;zero_time++)
{
// PWM的值必须清空
Pulse_Wave = 0;
SysTick_Delay_ms(200);
}
break;
}
// 等待两分钟若是没有变化则代表线掉了
else if((i >= (2*60)*1000/SAMPLE_TIME)&&(T<40))
{
/* 线掉了 */
LED_CYAN;
printf("线掉了....");
for(uint8_t zero_time=0;zero_time<5;zero_time++)
{
// PWM的值必须清空
Pulse_Wave = 0;
SysTick_Delay_ms(200);
}
while(1)
{
;;
}
}
}
}
while(1)
{
LED_RED;
T = DS18B20_Update_Temperature();
OLED_display_DS18B20(T);
// printf("Temperature-Pid:%.4f\n",T);
SysTick_Delay_ms(200);
}
}
// 全部软件配置
void All_Soft_Config()
{
// LED端口初始化
LED_GPIO_Config();
// 红灯亮
LED_RED;
//按键端口初始化
Key_GPIO_Config();
//定时器初始化,输出PWM波
TEMP_PWM_TIM_Init();
// 打开串口以输出调试信息
USART_Config();
// I2C GPIO引脚初始化
I2C_GPIO_Init();
// 配置DS18B20温度传感器
DS18B20_Configure();
// 配置OLED显示屏
OLED_SSD1306_Configure();
}
/* 只有当温度恰好在30度附近的时候才会启动函数 */
void Wait_Temperature_Init()
{
uint8_t i=0;
// 测量一次温度
T = DS18B20_Update_Temperature();
OLED_display_DS18B20(T);
printf("温度为%.4f℃\n",T);
// 温度大于30,一直等待直到温度在30度附近
if(T >= 30)
{
chill:
Pulse_Wave = 0;
LED_BLUE;
while (T > 30)
{
// 蓝灯闪烁等待温度降下来
LED3_TOGGLE
// DS18B20更新温度
T = DS18B20_Update_Temperature();
OLED_display_DS18B20(T);
printf("温度为%.4f℃\n",T);
SysTick_Delay_ms(1000);
}
// 青灯亮起
LED_CYAN;
SysTick_Delay_ms(1000);
}
// 温度小于30度
else
{
// 1000 加热
Pulse_Wave = 0.1*MAX_OUT;
LED_YELLOW;
// 加热到33度附近
while(T < 30)
{
i++;
// 黄灯闪烁
LED1_TOGGLE
LED2_TOGGLE;
// DS18B20更新温度
T = DS18B20_Update_Temperature();
OLED_display_DS18B20(T);
printf("温度为%.4f℃\n",T);
SysTick_Delay_ms(500);
/* 线掉了 */
if(i>2*60*2)
{
LED_CYAN;
printf("线掉了....\n");
Pulse_Wave = 0;
while(1)
{
;;
}
}
}
goto chill;
}
}
二、部分代码讲解
1、PID程序
PID程序已经在上一节介绍过了,不再赘述!
2、PWM输出
/******************bsp_pwm.h***********************/
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
/* PWM波占空比(0 ~ 10000) */
extern uint16_t Pulse_Wave;
// 选择TIM3通用定时器
#define TEMP_PWM_TIM TIM3
#define TEMP_PWM_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define TEMP_PWM_TIM_CLK RCC_APB1Periph_TIM3
// ARR的值为 10000,实际装载 Pulse_Wave 次,频率F = 100Hz
#define TEMP_PWM_TIM_Period (10000-1)
// 分频因子为 72-1
#define TEMP_PWM_TIM_Prescaler (72-1)
// PWM的脉冲宽度为 5000,占空比得出是50%
#define TEMP_PWM_TIM_Pulse Pulse_Wave
#define TEMP_PWM_TIM_CCRx CCR1
// TIM3 输出比较通道1
#define TEMP_PWM_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOC
#define TEMP_PWM_TIM_CH1_PORT GPIOC
#define TEMP_PWM_TIM_CH1_PIN GPIO_Pin_6
// TIM3中断配置
#define TEMP_PWM_TIMx_IRQn TIM3_IRQn //中断
#define TEMP_PWM_TIMx_IRQHandler TIM3_IRQHandler
/******************bsp_pwm.c***********************/
/* PWM波占空比(0~10000) */
uint16_t Pulse_Wave = 0;
/**
* @brief TIM定时器GPIO口配置
* @param 无
* @retval 无
*/
static void TEMP_PWM_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启重映射时钟(非常重要)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// 输出比较通道1 GPIO 初始化
RCC_APB2PeriphClockCmd(TEMP_PWM_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = TEMP_PWM_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(TEMP_PWM_TIM_CH1_PORT, &GPIO_InitStructure);
}
/**
* @brief 配置嵌套向量中断控制器NVIC
* @param 无
* @retval 无
*/
static void NVIC_Config_PWM(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置TIM3_IRQ中断为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = TEMP_PWM_TIMx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// * TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
static void TEMP_PWM_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
TEMP_PWM_TIM_APBxClock_FUN(TEMP_PWM_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
// 配置周期,这里配置为100K
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=TEMP_PWM_TIM_Period;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= TEMP_PWM_TIM_Prescaler;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(TEMP_PWM_TIM, &TIM_TimeBaseStructure);
// //改变指定管脚的映射 这里选择的是TIM3完全重映射(非常重要)
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3 ,ENABLE);
/*--------------------输出比较结构体初始化-------------------*/
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 空闲时的电平(低电平)
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
// 输出比较通道 1
TIM_OCInitStructure.TIM_Pulse = TEMP_PWM_TIM_Pulse;
TIM_OC1Init(TEMP_PWM_TIM, &TIM_OCInitStructure);
//使能预装载
TIM_OC1PreloadConfig(TEMP_PWM_TIM, TIM_OCPreload_Enable);
//使能TIM重载寄存器ARR
TIM_ARRPreloadConfig(TEMP_PWM_TIM, ENABLE);
// 使能计数器
TIM_Cmd(TEMP_PWM_TIM, ENABLE);
// 主输出使能(通用定时器不需要)
// TIM_CtrlPWMOutputs(TEMP_PWM_TIM, ENABLE);
//使能update中断
TIM_ITConfig(TEMP_PWM_TIM, TIM_IT_Update, ENABLE);
// PWM 中断设置
NVIC_Config_PWM();
}
// 定时器配置
void TEMP_PWM_TIM_Init(void)
{
// GPIO设置
TEMP_PWM_TIM_GPIO_Config();
// 定时器模式设置
TEMP_PWM_TIM_Mode_Config();
// 以往脉冲存储起来
Pulse_Update_Temp = Pulse_Wave;
}
/******************stm32f10x_it.c***********************/
extern float T;
/* PWM波中断服务函数 */
void TEMP_PWM_TIMx_IRQHandler(void)
{
if (TIM_GetITStatus(TEMP_PWM_TIM , TIM_IT_Update) != RESET) //TIM_IT_Update
{
// 温度达到120警戒
if(T >=120)
{
printf("DS18B20温度目前超过120度,必须停下来...");
// 让PWM波的值为0
TEMP_PWM_TIM -> TEMP_PWM_TIM_CCRx = 0;
LED_PURPLE;
// 程序在这卡死
while(1)
{
;;
}
}
if (Pulse_Update_Temp != Pulse_Wave)
{
// 修改CCR的值
TEMP_PWM_TIM -> TEMP_PWM_TIM_CCRx = Pulse_Wave; //根据PWM表修改定时器的比较寄存器值
Pulse_Update_Temp = Pulse_Wave;
}
TIM_ClearITPendingBit(TEMP_PWM_TIM, TIM_IT_Update); //必须要清除中断标志位
}
}
3、DS18B20传感器代码
/******************bsp_one_wire.h***********************/
#include "stm32f10x.h"
/************(单总线)DS18B20温度传感器相关************/
#define ONE_WIRE_GPIO_PORT GPIOB
#define ONE_WIRE_GPIO_PIN GPIO_Pin_9
#define RCC_ONE_WIRE_CLK RCC_APB2Periph_GPIOB
/******************bsp_one_wire.c***********************/
#include "bsp_one_wire.h"
#include "bsp_systick.h"
/**
* @brief (单总线)DS18B20温度传感器DQ口配置(开漏输出)
* @param 无
* @retval 无
*/
void DS18B20_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道1 GPIO 初始化
RCC_APB2PeriphClockCmd(RCC_ONE_WIRE_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ONE_WIRE_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ONE_WIRE_GPIO_PORT, &GPIO_InitStructure);
}
/**
* @brief DS18B20开始温度变换
* @param 无
* @retval 无
*/
void DS18B20_ConvertT(void)
{
// GPIO配置
DS18B20_GPIO_Config();
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
/**
* @brief DS18B20读取温度
* @param 无
* @retval 温度数值
*/
float DS18B20_ReadT(void)
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
TLSB=OneWire_ReceiveByte();
TMSB=OneWire_ReceiveByte();
Temp=(TMSB<<8)|TLSB;
T=Temp/16.0;
return T;
}
// 配置DS18B20
void DS18B20_Configure()
{
//上电先转换一次温度,防止第一次读数据错误
DS18B20_ConvertT();
//等待转换完成
SysTick_Delay_ms(1000);
}
// DS18B20更新温度
float DS18B20_Update_Temperature()
{
float T;
//转换温度
DS18B20_ConvertT();
//读取温度
T = DS18B20_ReadT();
// 返回温度值
return T;
}
4、OLED显示
/******************bsp_spi_oled.h***********************/
/* 等待超时时间 */
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/* 时钟 */
// SPI时钟
#define OLED_SSD1306_SPIx SPI1
#define OLED_SSD1306_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define OLED_SSD1306_SPI_CLK RCC_APB2Periph_SPI1
// OLED时钟
#define OLED_SSD1306_GPIO_CLK (OLED_SSD1306_SPI_SCK_CLK|OLED_SSD1306_SPI_MOSI_CLK |OLED_SSD1306_RES_CLK|OLED_SSD1306_DC_CLK|OLED_SSD1306_SPI_CS_CLK|SPI_MISO_CLK )
#define OLED_SSD1306_APBxClock_FUN RCC_APB2PeriphClockCmd
/* OLED(SSD1306)*/
// 时钟线-D0
#define OLED_SSD1306_SPI_SCK_GPIO_PORT GPIOA
#define OLED_SSD1306_SPI_SCK_GPIO_PIN GPIO_Pin_5
#define OLED_SSD1306_SPI_SCK_CLK RCC_APB2Periph_GPIOA
// 主机输出-D1
#define OLED_SSD1306_SPI_MOSI_GPIO_PORT GPIOA
#define OLED_SSD1306_SPI_MOSI_GPIO_PIN GPIO_Pin_7
#define OLED_SSD1306_SPI_MOSI_CLK RCC_APB2Periph_GPIOA
// 复位脚-RES
#define OLED_SSD1306_RES_GPIO_PORT GPIOA
#define OLED_SSD1306_RES_GPIO_PIN GPIO_Pin_4
#define OLED_SSD1306_RES_CLK RCC_APB2Periph_GPIOA
// 数据命令控制脚-DC
#define OLED_SSD1306_DC_GPIO_PORT GPIOE
#define OLED_SSD1306_DC_GPIO_PIN GPIO_Pin_6
#define OLED_SSD1306_DC_CLK RCC_APB2Periph_GPIOE
// 软件片选-CS
#define OLED_SSD1306_SPI_CS_GPIO_PORT GPIOE
#define OLED_SSD1306_SPI_CS_GPIO_PIN GPIO_Pin_5
#define OLED_SSD1306_SPI_CS_CLK RCC_APB2Periph_GPIOE
/* 主机输入(OLED没用上) */
#define SPI_MISO_GPIO_PORT GPIOA
#define SPI_MISO_GPIO_PIN GPIO_Pin_6
#define SPI_MISO_CLK RCC_APB2Periph_GPIOA
/* 片选拉高拉低 */
#define SPI_OLED_CS_LOW() GPIO_ResetBits(OLED_SSD1306_SPI_CS_GPIO_PORT, OLED_SSD1306_SPI_CS_GPIO_PIN)
#define SPI_OLED_CS_HIGH() GPIO_SetBits(OLED_SSD1306_SPI_CS_GPIO_PORT, OLED_SSD1306_SPI_CS_GPIO_PIN)
/* 数据命令控制脚拉高拉低 */
#define OLED_DC_LOW() GPIO_ResetBits(OLED_SSD1306_DC_GPIO_PORT, OLED_SSD1306_DC_GPIO_PIN)
#define OLED_DC_HIGH() GPIO_SetBits(OLED_SSD1306_DC_GPIO_PORT, OLED_SSD1306_DC_GPIO_PIN)
/* 复位引脚拉高拉低 */
#define OLED_RES_LOW() GPIO_ResetBits(OLED_SSD1306_RES_GPIO_PORT, OLED_SSD1306_RES_GPIO_PIN)
#define OLED_RES_HIGH() GPIO_SetBits(OLED_SSD1306_RES_GPIO_PORT, OLED_SSD1306_RES_GPIO_PIN)
/******************bsp_spi_oled.c***********************/
unsigned char OLED_GRAM[128][8] = {0};
/*
*********************************************************************************************************
* 函 数 名: OLED_SSD1306_Init
* 功能说明: OLED(SSD1306)初始化
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void OLED_SSD1306_Init(void)
{
// GPIO初始化
OLED_GPIO_Init();
// SPI初始化
OLED_SPI_Init();
//延时1秒稳定端口状态
SysTick_Delay_ms(1000);
/* 复位 */
OLED_RES_LOW();
SysTick_Delay_ms(10);
/* 复位正常信号 SSD1306: RES引脚高电平 */
OLED_RES_HIGH();
OLED_Write_Cmd(0xAE); //关闭显示
OLED_Write_Cmd(0x20); //设置模式
OLED_Write_Cmd(0x10); //设置为页显示模式
OLED_Write_Cmd(0xb0); // 设置起始页的地址模式 0-7
OLED_Write_Cmd(0xc8); // 0xc9上下反置 0xc8正常
OLED_Write_Cmd(0x00); // ---set low column address
OLED_Write_Cmd(0x10); // ---set high column address
OLED_Write_Cmd(0x40); //--set start line address
OLED_Write_Cmd(0x81); //--set contrast control register
OLED_Write_Cmd(0xff); //亮度调节 0x00~0xff
OLED_Write_Cmd(0xa1); // 0xa0左右反置 0xa1正常
OLED_Write_Cmd(0xa6); //设置显示方式;bit0:1,反相显示;0,正常显示
OLED_Write_Cmd(0xa8); //--set multiplex ratio(1 to 64)
OLED_Write_Cmd(0x3F); //
OLED_Write_Cmd(0xa4); //全局显示开启;0xa4正常,0xa5无视命令点亮全屏
OLED_Write_Cmd(0xd3); //-set display offset
OLED_Write_Cmd(0x00); //-not offset
OLED_Write_Cmd(0xd5); //设置时钟分频因子,震荡频率
OLED_Write_Cmd(0xf0); //[3:0],分频因子;[7:4],震荡频率
OLED_Write_Cmd(0xd9); //--set pre-charge period
OLED_Write_Cmd(0x22); //
OLED_Write_Cmd(0xda); //--set com pins hardware configuration
OLED_Write_Cmd(0x12);
OLED_Write_Cmd(0xdb); //--set vcomh
OLED_Write_Cmd(0x20); //0x20,0.77xVcc
// OLED_Write_Cmd(0x81); //设置对比度
// OLED_Write_Cmd(0x7f); // 128
OLED_Write_Cmd(0x8d); //设置电荷泵开关
OLED_Write_Cmd(0x14); //开
OLED_Write_Cmd(0xaf); //开启显示
}
/**
* @brief OLED_SetPos,设置光标
* @param x,光标x位置
* y,光标y位置
* @retval 无
*/
void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
{
OLED_Write_Cmd(0xb0+y);
OLED_Write_Cmd(((x&0xf0)>>4)|0x10);
OLED_Write_Cmd((x&0x0f)|0x01);
}
/**
* @brief OLED_Fill,填充整个屏幕
* @param fill_Data:要填充的数据
* @retval 无
*/
void OLED_Fill(unsigned char fill_Data)//全屏填充
{
unsigned char m,n;
for(m=0;m<8;m++)
{
OLED_Write_Cmd(0xb0+m); //page0-page1
OLED_Write_Cmd(0x00); //low column start address
OLED_Write_Cmd(0x10); //high column start address
for(n=0;n<128;n++)
{
OLED_Write_Data(fill_Data);
OLED_GRAM[n][m] = fill_Data;
}
}
}
/**
* @brief OLED_CLS,清屏
* @param 无
* @retval 无
*/
void OLED_CLS(void)//清屏
{
OLED_Fill(0x00);
}
/**
* @brief OLED_Refresh_Gram,刷新整个屏幕数组并显示
* @param 无
* @retval 无
*/
void OLED_Refresh_Gram(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
// 起始点开始全部刷新
OLED_Write_Cmd(0xb0+i); //设置页地址(0~7)
OLED_Write_Cmd(0x00); //设置显示位置—列低地址
OLED_Write_Cmd(0x10); //设置显示位置—列高地址
for(n=0;n<128;n++) //写一PAGE的GDDRAM数据
{
// 设置起始点坐标
OLED_Write_Data(OLED_GRAM[n][i]);
}
}
}
/**
* @brief OLED_Part_Refresh_Gram,刷新部分屏幕数组并显示
* @param i : 第i页位置,(0~7);
* n0,n1 : 从第n0列开始刷新,刷新到第n1列
* @retval 无
*/
void OLED_Part_Refresh_Gram(unsigned char i,unsigned char n0,unsigned char n1)
{
// 起始的点开始刷新
OLED_SetPos(n0-1, i);
// 写部分数组的数据
for(;n0-1<n1+1;n0++)
{
// 设置起始点坐标
OLED_Write_Data(OLED_GRAM[n0-1][i]);
}
}
/**
* @brief OLED_DrawDot,画点函数
* @param x,y : 绘画点的坐标(x:0~127, y:0~63);
* t : 0表示该像素不显示,1表示该像素显示 , -1表示像素点直接取反
* @retval 无
*/
void OLED_DrawDot(unsigned char x,unsigned char y,unsigned char t)
{
unsigned char pos,bx,temp=0;
// 此OLED的分辨率为128*64,横坐标大于127,纵坐标大于63,则参数非法
if(x>127||y>63) return;
// 因为此OLED是按页显示,每页8个像素,所以/8用于计算待显示的点在哪页中
pos=(y)/8;
// 一列中有8个像素,所以计算一下待显示的点,在当前列中的第几个点
bx=y%8;
// 移位,让temp的第bx位为1
temp=1<<(bx);
if(t==1)
{
OLED_GRAM[x][pos]|=temp; //第bx位,置1,其他位值不变
}
else if(t==0)
{
OLED_GRAM[x][pos]&=~temp; //第bx位,置0,其他位值不变
}
else
{
OLED_GRAM[x][pos]^=temp; //第bx位,直接异或取反
}
// // 刷新整个液晶屏
OLED_Refresh_Gram();
// 部分刷新屏幕(这样会快)
// OLED_Part_Refresh_Gram(pos,x,x);
}
/**
* @brief OLED_DrawLine,画线函数
* @param x1,y1 : 起始点坐标(x1:0~127, y1:0~63);
* x2,y2 : 终点(结束点)的坐标(x2:0~128,y2:0~63)
* m : 1 为直接点亮,0 为直接点灭,-1为直接取反
* @retval 无
*/
void OLED_DrawLine(unsigned int x1, unsigned int y1, unsigned int x2,unsigned int y2, unsigned int m)
{
unsigned int t;
int offset_x,offset_y;
int incx,incy,uRow,uCol;
float K = 0.0f;
offset_x=x2-x1;
offset_y=y2-y1;
uRow=x1;
uCol=y1;
if(offset_x>0)
{
incx=1;
}
else if(offset_x==0)
{
incx=0; //垂直线
}
else
{
incx=-1;
offset_x=-offset_x;
}
if(offset_y>0)
{
incy=1;
}
else if(offset_y==0)
{
incy=0; //水平线
}
else
{
incy=-1;
offset_y=-offset_y;
}
// 垂直线
if(incx==0)
{
for(t=0;t<=offset_y+1;t++ )
{
OLED_DrawDot(uRow,uCol+t*incy,m);
}
}
// 水平线
else if(incy==0)
{
for(t=0;t<=offset_x+1;t++ )
{
OLED_DrawDot(uRow+t*incx,uCol,m);
}
}
else
{
K = (float)(((float)y2-(float)y1)*1.000/((float)x2-(float)x1));
printf("K=%.3f\r\n",K);
for(t=0;t<=offset_x+1;t++ )
{
printf("X=%d,Y=%d\r\n",uRow+t,(uint8_t)(uCol+t*K));
OLED_DrawDot(uRow+t,(uint8_t)(uCol+t*K),m);
}
}
}
/**
* @brief OLED_Horizontal_Scroll,水平向左滚动函数(1个单位)
* @param page : 滚动的起始页码(page:0~7);
* @retval 无
*/
void OLED_Horizontal_Scroll_One(uint8_t page)
{
// 搬运部分数据
for(uint8_t i=0;i<127;i++)
{
memcpy(&OLED_GRAM[i][page],&OLED_GRAM[i+1][page],sizeof(unsigned char)*(8-page));
}
// 向右滚动后最后一列数据清空(需要验证)
for(uint8_t i=page;i<8;i++) //写一PAGE的GDDRAM数据
{
// 最后一列清空
OLED_GRAM[127][i] = 0x00;
}
}
/**
* @brief OLED_Coordinate_Display,显示坐标函数,以右下角为原点
* @param x,y : 绘坐标(x:0~127, y:0~63);
* @retval 无
*/
// 测试一下,需要去有符号(是否出错)
void OLED_Coordinate_Display(uint8_t x,float T)
{
uint8_t y;
#if 0
if((T >= 46) && (T <= 77.5))
{
// 0.5°C为一个像素点,最高显示是77.5°C,最低显示是46°C
// 坐标转换
y = 63 - (T-46)/0.5;
// 先描点
OLED_DrawDot(x,y,-1);
// 显示温度顶线 70°C
OLED_Operation_Line(1, 7, 1);
// 刷新显示函数
OLED_Refresh_Gram();
}
else if(T < 46)
{
// 只有在达到46度时才会显示图像
//i = 0;
OLED_ShowStr(0,4,(uint8_t *)"Display temper",2);
OLED_ShowStr(0,6,(uint8_t *)"not reached.",2);
// 显示当前温度
OLED_display_DS18B20(T);
}
#else
if((T >= 58) && (T <= 73.75))
{
// 0.2°C为一个像素点,最高显示是73.75°C,最低显示是58°C
// 坐标转换
y = 63 - (T-58)/0.25;
// 先描点
OLED_DrawDot(x,y,-1);
// 显示温度顶线 70°C
OLED_Operation_Line(1, 7, 1);
// 刷新显示函数
OLED_Refresh_Gram();
}
else if(T < 58)
{
// 只有在达到46度时才会显示图像
//i = 0;
OLED_ShowStr(0,4,(uint8_t *)"Display temper",2);
OLED_ShowStr(0,6,(uint8_t *)"not reached.",2);
// 显示当前温度
OLED_display_DS18B20(T);
}
#endif
}
// 配置OLED显示屏
void OLED_SSD1306_Configure()
{
// OLED 初始化
OLED_SSD1306_Init();
//全屏点亮
OLED_Fill(0xFF);
//SysTick_Delay_ms(1000);
//全屏灭
OLED_Fill(0x00);
//SysTick_Delay_ms(1000);
}
// OLED 显示温度
void OLED_display_DS18B20(float T)
{
char str1[20];
// 显示当前温度
sprintf(str1, "Temp:%.4f",T);
OLED_ShowStr(0,0,(uint8_t *)str1,2);
//printf("温度为%.4f℃\n",T);
}
// OLED 显示温度线
void OLED_display_DS18B20_line(int i,float T)
{
// 当列数达到120时
if(i<120)
{
// 在第i横排点显示温度(有可显示的范围值)
OLED_Coordinate_Display(i,T);
}
else
{
// 滚动起来
OLED_Horizontal_Scroll_One(0);
// 始终在第120列显示
OLED_Coordinate_Display(120,T);
}
}
总结
本节重点讲述了部分实验代码,稍后我会将完整代码放在评论区!下一节,将为大家带来最终实验现象和总结!敬请期待!
这一节的代码源文件和VOFA++控件,我将稍后会放在评论区,需要的自取!!!