1 项目需求
- 使用MSPM0L1306的定时器,生成2路重复频率1Hz~1MHz的PWM信号,每一路的重复频率和占空比都可独立调节。
重复频率 | 占空比调节精度 |
1MHz | 1/10 |
100KHz | 1/100 |
10KHz | 1/1,000 |
1KHz | 1/10,000 |
100Hz | 1/10,000 |
1Hz | 1/10,000 |
PWM重复频率在1Hz~10KHz时,占空比的精度不低于1/1000,100Khz和1Mhz的占空比精度按照表格即可。
- 使用按键或拨码开关组合调节输出频率、占空比,并由按键控制每一路PWM信号的输出。
- 能在OLED屏幕上显示基础信息,当前使用引脚示意、引脚相应的PWM参数。
- 将PWM参数(包括两个通道的频率占空比、及通道工作与否)按照1s左右的周期,通过串口发送到上位机。
- 其中,PWM重复频率在1Hz~10KHz内连续可调,通过按键切换至100KHz和1MHz。
- PWM重复频率越高,占空比分辨率降低(主时钟32MHz为例)
2 完成的功能及达到的性能
2.1 输出两路PWM波
配置sysconfig的定时器PWM输出(目前配置了通道一:PA21,通道二:PA26),使用两个不同的定时器,从而实现两个不同通道的重复频率和占空比的独立调节
2.2 调节重复频率
实现1Hz~1MHz的PWM重复频率连续可调,频率计算与sysconfig一致(ARR=SYSCLK/freq+1
(注意:调整重复频率通过调整预装载寄存器的值实现,会导致占空比发生变化,通过提前取出占空比并重新计算赋值,尽可能保证占空比不变,如需更加精确,请重新调节占空比)
2.3 调节占空比
实现精度不低于0.0001%的占空比连续调节,通过计算,完全符合规则要求的“对应持续时间的精度”达到表格中的要求
2.4 PWM波输出与否
通过操作PWM的时钟开关来实现对PWM波输出与否的控制
2.5 矩阵键盘与多级菜单
采用扫描的方式实现4*4矩阵键盘,通过矩阵键盘与OLED显示屏实现多级菜单,可操作调节输出频率、占空比,以及控制每一路PWM信号的输出。
2.6 占空比精度
占空比精度通过浮点运算以及标准计算公式尽可能逼近实际。
2.7 定时器中断和串口输出
3 实现思路
- 两个独立的定时器分别生成一路PWM信号
- 将信号相关参数在OLED上边显示
- 按下按键操作菜单独立调整两路信号的重复频率、占空比和输出与否。
- 将信号相关变化参数在OLED上边显示
4 实现过程
4.1 程序流程图
4.2 OLED函数
为了大家查看方便,我就不把例程中已经有的放出来了,说一说自己写的
为了方便编辑,我使用了新的字库数组,应用到新的显示中文的函数当中,当然逻辑上是一样的,但是这个函数说实话不是很好用,每次都只能显示一个字,而且还不支持字符串直接输出,要写上数组下标才行。
//显示汉字
void OLED_ShowMyCHinese(u8 x,u8 y,u8 no)
{
u8 t,adder=0;
OLED_Set_Pos(x,y);
for(t=0;t<16;t++)
{
OLED_WR_Byte(MYlib[2*no][t],OLED_DATA);
adder+=1;
}
OLED_Set_Pos(x,y+1);
for(t=0;t<16;t++)
{
OLED_WR_Byte(MYlib[2*no+1][t],OLED_DATA);
adder+=1;
}
}
除此之外,为了更方便的显示浮点数,我编写了一个显示小数点函数
//显示小数
void OLED_ShowFloatNum(uint8_t X, uint8_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize)
{
uint32_t PowNum, IntNum, FraNum;
/*提取整数部分和小数部分*/
IntNum = Number; //直接赋值给整型变量,提取整数
Number -= IntNum; //将Number的整数减掉,防止之后将小数乘到整数时因数过大造成错误
PowNum = oled_pow(10, FraLength); //根据指定小数的位数,确定乘数
FraNum = round(Number * PowNum); //将小数乘到整数,同时四舍五入,避免显示误差
IntNum += FraNum / PowNum; //若四舍五入造成了进位,则需要再加给整数
/*显示整数部分*/
OLED_ShowNum(X, Y, IntNum, IntLength, FontSize);
/*显示小数点*/
OLED_ShowChar(X + (IntLength) * FontSize/2, Y, '.');
/*显示小数部分*/
OLED_ShowNum(X + (IntLength + 1) * FontSize/2, Y, FraNum, FraLength, FontSize);
}
最后附上新增字库
unsigned char MYlib[][32]={
{0x40,0x42,0x44,0xcc,0x00,0x00,0xf1,0x91,0x95,0xf9,0x95,0x93,0xf9,0x10,0x00,0x00},
{0x00,0x40,0x20,0x1f,0x20,0x40,0xbf,0x84,0x84,0xbf,0x94,0xa4,0x9f,0xc0,0x40,0x00},//通0
{0x40,0x42,0x44,0xcc,0x00,0x04,0xe5,0xb6,0xac,0xa4,0xa4,0xa6,0xf5,0x24,0x00,0x00},
{0x00,0x40,0x20,0x1f,0x20,0x40,0xbf,0xa4,0xa4,0xa4,0xa4,0xa4,0xbf,0xc0,0x40,0x00},//道1
{0x40,0x7c,0x40,0xff,0x48,0x6c,0x4a,0xf2,0x12,0x1a,0xd6,0x12,0x12,0xfb,0x12,0x00},
{0x08,0x87,0x40,0x2f,0x10,0x0f,0x80,0x8f,0x40,0x20,0x1f,0x20,0x40,0xcf,0x00,0x00},//频2
{0x00,0x14,0xa4,0x44,0x04,0x24,0xb5,0x6e,0x24,0x94,0x04,0x44,0xa6,0x14,0x00,0x00},
{0x08,0x09,0x08,0x08,0x09,0x09,0x09,0xff,0x09,0x09,0x0b,0x08,0x08,0x0d,0x08,0x00},//率3
{0x00,0x00,0x80,0x80,0x80,0x80,0xff,0x88,0x88,0x88,0x88,0xc8,0x8c,0x08,0x00,0x00},
{0x00,0x00,0xff,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xff,0x00,0x00,0x00,0x00},//占4
{0x10,0x0c,0x84,0x44,0x24,0x34,0x05,0x06,0x04,0x14,0x24,0x64,0xc4,0x14,0x0c,0x00},
{0x40,0x40,0x42,0x42,0x42,0x42,0x42,0x7e,0x42,0x42,0x42,0x42,0x42,0x60,0x40,0x00},//空5
{0x00,0x00,0xfe,0x40,0x40,0x60,0x40,0x00,0xff,0x80,0x40,0x20,0x30,0x00,0x00,0x00},
{0x00,0x40,0x7f,0x20,0x20,0x10,0x10,0x00,0x3f,0x40,0x40,0x40,0x40,0x40,0x78,0x00},//比6
{0x00,0x08,0x30,0x00,0xff,0x20,0x20,0x20,0x20,0xff,0x20,0x22,0x24,0x30,0x20,0x00},
{0x08,0x0c,0x02,0x01,0xff,0x40,0x20,0x1c,0x03,0x00,0x03,0x0c,0x30,0x60,0x20,0x00},//状7
{0x04,0x04,0x84,0x84,0x44,0x24,0x54,0x8f,0x14,0x24,0x44,0x44,0x84,0x86,0x84,0x00},
{0x01,0x21,0x1c,0x00,0x3c,0x40,0x42,0x4c,0x40,0x40,0x70,0x04,0x08,0x31,0x00,0x00},//态8
{0x80,0x82,0x82,0x82,0xfe,0x82,0x82,0x82,0x82,0x82,0xfe,0x82,0x83,0xc2,0x80,0x00},
{0x00,0x80,0x40,0x30,0x0f,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00},//开9
{0x00,0x00,0x00,0xfc,0x44,0x44,0x44,0x45,0x46,0x44,0x44,0x44,0x44,0x7e,0x04,0x00},
{0x40,0x20,0x18,0x07,0xfe,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0xff,0x02,0x00},//启10
{0x00,0x10,0x10,0x11,0x12,0x1c,0x10,0xf0,0x10,0x18,0x14,0x13,0x1a,0x90,0x00,0x00},
{0x81,0x81,0x41,0x41,0x21,0x11,0x0d,0x03,0x0d,0x11,0x21,0x21,0x41,0xc1,0x41,0x00},//关11
{0x00,0xf8,0x01,0x22,0x26,0x20,0x22,0xa2,0xfa,0x22,0x32,0x22,0x02,0xff,0x02,0x00},
{0x00,0xff,0x00,0x08,0x04,0x02,0x21,0x40,0x3f,0x00,0x00,0x40,0x80,0x7f,0x00,0x00},//闭12
{0x04,0x34,0xc4,0x04,0xc4,0x3c,0x20,0x10,0x0f,0xe8,0x08,0x08,0x28,0x18,0x00,0x00},
{0x10,0x08,0x06,0x01,0x82,0x8c,0x40,0x30,0x0c,0x03,0x0c,0x10,0x60,0xc0,0x40,0x00},//欢13
{0x40,0x42,0x44,0xc8,0x00,0xfc,0x04,0x02,0x82,0xfc,0x04,0x04,0x04,0xfe,0x04,0x00},
{0x00,0x40,0x20,0x1f,0x20,0x47,0x42,0x41,0x40,0x7f,0x40,0x42,0x44,0x63,0x20,0x00},//迎14
{0x40,0x20,0xf8,0x07,0x04,0xf4,0x14,0x14,0x14,0xff,0x14,0x14,0x14,0xf6,0x04,0x00},
{0x00,0x00,0xff,0x00,0x80,0x43,0x45,0x29,0x19,0x17,0x21,0x21,0x41,0xc3,0x40,0x00},//使15
{0x00,0x00,0xfe,0x22,0x22,0x22,0x22,0xfe,0x22,0x22,0x22,0x22,0xff,0x02,0x00,0x00},
{0x80,0x60,0x1f,0x02,0x02,0x02,0x02,0x7f,0x02,0x02,0x42,0x82,0x7f,0x00,0x00,0x00},//用16
{0x04,0x34,0xc4,0x04,0xc4,0x3c,0x00,0x04,0x7c,0x84,0x04,0x04,0xc4,0x3e,0x04,0x00},
{0x40,0x30,0x0c,0x03,0x0c,0xb0,0x80,0x40,0x20,0x13,0x0c,0x1a,0x61,0xc0,0x40,0x00},//双17
{0x00,0x10,0x18,0x16,0x10,0x90,0xf0,0x9f,0x90,0x90,0x92,0x94,0x10,0x18,0x10,0x00},
{0x40,0x20,0x90,0x88,0x46,0x41,0x23,0x14,0x08,0x14,0x22,0x21,0x40,0xc0,0x40,0x00},//发18
{0x00,0xc0,0x30,0x1e,0x10,0x10,0x10,0xff,0x10,0x10,0x10,0x10,0x18,0x10,0x00,0x00},
{0x41,0x40,0x42,0x42,0x42,0x42,0x42,0x7f,0x42,0x42,0x42,0x43,0x42,0x60,0x40,0x00},//生19
{0x80,0x80,0x9f,0x91,0x91,0x91,0x9f,0xe0,0x9f,0x91,0xb1,0xd1,0x9f,0xc0,0x80,0x00},
{0x08,0x08,0xfc,0x8c,0x8a,0x8a,0xf9,0x00,0xf9,0x8a,0x8a,0x8c,0xfc,0x08,0x08,0x00},//器20
{0x84,0x84,0x44,0xe4,0x54,0x4c,0x44,0x7f,0x44,0x4c,0x54,0xe4,0x44,0xc6,0x44,0x00},
{0x40,0x40,0x40,0x5f,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x5f,0x40,0x60,0x40,0x00},//查21
{0x20,0x2a,0x2a,0xaa,0xea,0xba,0xae,0xaa,0xaa,0xa9,0xa9,0xed,0xa8,0x20,0x20,0x00},
{0x04,0x02,0x01,0xff,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0xff,0x00,0x00,0x00,0x00},//看22
{0x20,0x22,0xec,0x00,0x20,0x22,0xaa,0xaa,0xaa,0xbf,0xaa,0xaa,0xeb,0xa2,0x20,0x00},
{0x00,0x00,0x7f,0x20,0x10,0x00,0xff,0x0a,0x0a,0x0a,0x4a,0x8a,0x7f,0x00,0x00,0x00},//请23
{0x10,0x10,0x90,0xff,0x90,0xa0,0x98,0x88,0x88,0xe9,0x8a,0x88,0x88,0xa8,0x98,0x00},
{0x01,0x41,0x80,0x7f,0x00,0x00,0x80,0x84,0x4b,0x30,0x10,0x28,0x47,0xc0,0x00,0x00},//按24
{0x80,0x40,0x20,0xf8,0x07,0x24,0x24,0x24,0x25,0x26,0x24,0x24,0xb4,0x26,0x04,0x00},
{0x00,0x00,0x00,0xff,0x00,0x01,0xfd,0x45,0x45,0x45,0x45,0x45,0xfd,0x01,0x00,0x00},//信25
{0x80,0x80,0x80,0xbe,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xbf,0x82,0xc0,0x80,0x00},
{0x00,0x00,0x00,0x04,0x06,0x05,0x04,0x04,0x44,0x84,0x44,0x3e,0x04,0x00,0x00,0x00},//号26
};
4.3 菜单总览
总共四个Page,实现菜单显示
void page0(void)//开机画面
{
OLED_ShowMyCHinese(36, 1, 13);OLED_ShowMyCHinese(50, 1, 14);OLED_ShowMyCHinese(66, 1, 15);OLED_ShowMyCHinese(82, 1, 16);
OLED_ShowMyCHinese(42, 3, 17);OLED_ShowMyCHinese(58, 3, 0);OLED_ShowMyCHinese(74, 3, 1);
OLED_ShowString(10, 5, "PWM");OLED_ShowMyCHinese(34, 5, 25);OLED_ShowMyCHinese(50, 5, 26);OLED_ShowMyCHinese(66, 5, 18);OLED_ShowMyCHinese(82, 5, 19);OLED_ShowMyCHinese(98, 5, 20);
}
void page1(void)//主页
{
OLED_ShowMyCHinese(2, 0, 21);OLED_ShowMyCHinese(18, 0, 22);OLED_ShowMyCHinese(34, 0, 0);OLED_ShowMyCHinese(50, 0, 1);OLED_ShowString(66, 0, "1(PA21)");
OLED_ShowMyCHinese(2, 2, 23);OLED_ShowMyCHinese(18, 2, 24);OLED_ShowString(34, 2, "S11");
OLED_ShowMyCHinese(2, 4, 21);OLED_ShowMyCHinese(18, 4, 22);OLED_ShowMyCHinese(34, 4, 0);OLED_ShowMyCHinese(50, 4, 1);OLED_ShowString(66, 4, "2(PA26)");
OLED_ShowMyCHinese(2, 6, 23);OLED_ShowMyCHinese(18, 6, 24);OLED_ShowString(34, 6, "S12");
//设置相关值
//通道1
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
freq1=MYCLK/ARR1;
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
//通道2
ARR2=DL_TimerG_getLoadValue(PWM_2_INST)+1;
freq2=MYCLK/ARR2;
CCR2=DL_TimerG_getCaptureCompareValue(PWM_2_INST,GPIO_PWM_2_C0_IDX);
duty2=(double)CCR1/ARR2*100;
}
void page2(void)//通道1详情页
{
OLED_ShowMyCHinese(2, 0, 0);OLED_ShowMyCHinese(18, 0, 1);OLED_ShowString(34, 0, "1");
OLED_ShowMyCHinese(42, 0, 7);OLED_ShowMyCHinese(58, 0, 8);OLED_ShowString(74, 0, ":");
if(DL_Timer_isClockEnabled(PWM_1_INST)==true)
{
OLED_ShowMyCHinese(82, 0, 9);OLED_ShowMyCHinese(98, 0, 10);//9、10开启
}
if(DL_Timer_isClockEnabled(PWM_1_INST)==false)
{
OLED_ShowMyCHinese(82, 0, 11);OLED_ShowMyCHinese(98, 0, 12);//11、12关闭
}
//频率
OLED_ShowMyCHinese(2, 2, 2);OLED_ShowMyCHinese(18, 2, 3);OLED_ShowString(34, 2, ":");
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
freq1=(double)MYCLK/ARR1;
OLED_ShowNum(42,2,freq1,8,16);OLED_ShowString(106, 2, "Hz");
//占空比
OLED_ShowMyCHinese(2, 4, 4);OLED_ShowMyCHinese(18, 4, 5);OLED_ShowMyCHinese(34, 4, 6);OLED_ShowString(50, 4, ":");
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
OLED_ShowFloatNum(58,4,duty1,3,4,16);OLED_ShowString(122, 4, "%");
}
void page3(void)//通道2详情页
{
OLED_ShowMyCHinese(2, 0, 0);OLED_ShowMyCHinese(18, 0, 1);OLED_ShowString(34, 0, "2");
OLED_ShowMyCHinese(42, 0, 7);OLED_ShowMyCHinese(58, 0, 8);OLED_ShowString(74, 0, ":");
if(DL_Timer_isClockEnabled(PWM_2_INST)==true)
{
OLED_ShowMyCHinese(82, 0, 9);OLED_ShowMyCHinese(98, 0, 10);//9、10开启
}
if(DL_Timer_isClockEnabled(PWM_2_INST)==false)
{
OLED_ShowMyCHinese(82, 0, 11);OLED_ShowMyCHinese(98, 0, 12);//11、12关闭
}
//频率
OLED_ShowMyCHinese(2, 2, 2);OLED_ShowMyCHinese(18, 2, 3);OLED_ShowString(34, 2, ":");
ARR2=DL_TimerG_getLoadValue(PWM_2_INST)+1;
freq2=(double)MYCLK/ARR2;
OLED_ShowNum(42,2,freq2,8,16);OLED_ShowString(106, 2, "Hz");
//占空比
OLED_ShowMyCHinese(2, 4, 4);OLED_ShowMyCHinese(18, 4, 5);OLED_ShowMyCHinese(34, 4, 6);OLED_ShowString(50, 4, ":");
CCR2=DL_TimerG_getCaptureCompareValue(PWM_2_INST,GPIO_PWM_2_C0_IDX);
duty2=(double)CCR2/ARR2*100;
OLED_ShowFloatNum(58,4,duty2,3,4,16);OLED_ShowString(122, 4, "%");
}
4.3 矩阵键盘
采用行输出(行扫描),列输入的方式获取键码并返回,基本与例程一致,需要行上拉输出。
int getKeyValue(void)
{
int h_arr[4] = {KEY_H1_PIN, KEY_H2_PIN, KEY_H3_PIN, KEY_H4_PIN};
int v_arr[4] = {KEY_V1_PIN, KEY_V2_PIN, KEY_V3_PIN, KEY_V4_PIN};
int i, j = 0;
int key_value = 0;
for (i = 0; i < 4; i++)
{
delay_cycles(1000);
DL_GPIO_setPins(KEY_PORT, h_arr[i]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 1) % 4]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 2) % 4]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 3) % 4]);
delay_cycles(100000);
for (j = 0; j < 4; j++)
{
if (DL_GPIO_readPins(KEY_PORT, v_arr[j]) != 0)
{
key_value = j * 4 + i + 1;
}
delay_cycles(1000);
}
delay_cycles(1000);
}
return key_value; // 没有按下,返回0
}
4.4 菜单操作实现
主页选项实现(第一级,主函数)
//主函数
int main(void)
{
SYSCFG_DL_init(); // Initialize the device
DL_TimerG_startCounter(PWM_1_INST);
DL_TimerG_startCounter(PWM_2_INST);
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
DL_TimerG_startCounter(TIMER_0_INST);//开启定时中断
OLED_Init();
page0();
delay_cycles(32000000);
OLED_Clear();
while(1)
{
page1();
key_value1 = getKeyValue();
switch(key_value1)
{
case 11:while(getKeyValue()==11);channel1();break;
case 12:while(getKeyValue()==12);channel2();break;
default:if(key_value1!=0 & key_value1!=15){OLED_ShowString(2, 6, "INPUT ERROR!!!");delay_cycles(8000000);OLED_ShowString(2, 6, " ");}break;
}
}
}
通道1选项实现
void channel1()
{
int channel1_key_value1;
OLED_Clear();
page2();
while(1)
{
OLED_ShowMyCHinese(2,6,7);OLED_ShowString(18,6,":11");OLED_ShowMyCHinese(42,6,2);OLED_ShowString(58,6,":13");OLED_ShowMyCHinese(82,6,4);OLED_ShowString(98,6,":14");
channel1_key_value1 = getKeyValue();
switch(channel1_key_value1)
{
case 11:while(getKeyValue()==11);StartOrStop1();break;
case 13:while(getKeyValue()==13);changefreq1();break;
case 14:while(getKeyValue()==14){OLED_ShowString(2,6," ");OLED_ShowString(2,6,"SWITCH11 is '.'");}changeduty1();break;
case 15:while(getKeyValue()==15);OLED_Clear();return;
default:if(channel1_key_value1!=0 & channel1_key_value1!=11){OLED_ShowString(2, 6, "INPUT ERROR!!!");delay_cycles(8000000);OLED_ShowString(2, 6, " ");}break;
}
}
}
通道2选项实现
void channel2()
{
int channel2_key_value1;
OLED_Clear();
page3();
while(1)
{
OLED_ShowMyCHinese(2,6,7);OLED_ShowString(18,6,":11");OLED_ShowMyCHinese(42,6,2);OLED_ShowString(58,6,":13");OLED_ShowMyCHinese(82,6,4);OLED_ShowString(98,6,":14");
channel2_key_value1 = getKeyValue();
switch(channel2_key_value1)
{
case 12:while(getKeyValue()==12);StartOrStop2();break;
case 13:while(getKeyValue()==13);changefreq2();break;
case 14:while(getKeyValue()==14){OLED_ShowString(2,6," ");OLED_ShowString(2,6,"SWITCH11 is '.'");};changeduty2();break;
case 15:while(getKeyValue()==15);OLED_Clear();return;
default:if(channel2_key_value1!=0 & channel2_key_value1!=12){OLED_ShowString(2, 6, "INPUT ERROR!!!");delay_cycles(8000000);OLED_ShowString(2, 6, " ");}break;
}
}
}
调整通道一的频率和占空比
void changeduty1(void)
{
int Count1 = 0;
int Count2 = 0;
int key_staus = 0;
double pow1=0;
double temp1=0;
double temp2=0;
double result=0;
int key_value2 = 0;
while(1){
key_value2 = getKeyValue();
if(key_value2 != 0)
{
delay_cycles(640000);
if(key_value2==11) //如果S11按键按下,输入小数点
{
while(getKeyValue()!=0);//确保单击只输入一个值
// OLED_ShowString(58+(Count1)*8,4,"."); //更新显示
key_staus=1;
}
if((key_value2<=10) & (key_staus==0)) //标志位无效,S1~S10按键按下,输入整数部分值
{
while(getKeyValue()!=0);//确保单击只输入一个值
if(Count1<3) //如果输入次数小于3
{
temp1*=10; //总输入值左移一位
temp1+=key_value2%10; //获取一位输入
Count1++; //计次1加一
}
delay_cycles(640000);
OLED_ShowString(74,4,"0.0000"); //更新显示
OLED_ShowNum(58,4,temp1,3,16); //更新显示
}
if((key_value2<=10) & (key_staus==1)) //标志位有效,S1~S10按键按下,输入小数部分值
{
while(getKeyValue()!=0);//确保单击只输入一个值
if(Count2<4) //如果输入次数小于4
{
temp2*=10; //总输入值右移一位
temp2+=key_value2%10; //获取一位输入
Count2++; //计次2加一
}
delay_cycles(640000);
OLED_ShowString(90,4,"0000"); //更新显示
OLED_ShowNum(90,4,temp2,Count2,16); //更新显示
}
if(key_value2==16) //如果S16按键按下,确认
{
while(getKeyValue()!=0);//确保单击只输入一个值
OLED_ShowNum(0,6,temp1,4,16); //更新显示
OLED_ShowFloatNum(64,6,temp2/oled_pow(10,Count2),2,4,16); //更新显示
result = temp1 + (temp2/oled_pow(10,Count2));
OLED_ShowFloatNum(58,4,result,3,Count2,16); //更新显示
delay_cycles(640000);
if(result<=100) //如果占空比小于100%
{
duty1=result;
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;;
CCR1=ARR1*duty1/100;
DL_TimerG_setCaptureCompareValue(PWM_1_INST,CCR1,GPIO_PWM_1_C0_IDX);//设置捕获比较值
result=0; //运算结果清零
Count1=0; //计次1清零
temp1=0; //中间值1清零
temp2=0; //中间值2清零
return;
}
else if(result>100) //否则
{
result=0; //运算结果清零
Count1=0; //计次1清零
temp1=0; //中间值1清零
temp2=0; //中间值2清零
OLED_ShowString(58,4," ERROR "); //更新显示
delay_cycles(16000000);//延时500ms
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
OLED_ShowFloatNum(58,4,duty1,3,4,16); //更新显示
return;
}
}
if(key_value2==15) //如果S15按键按下,取消
{
while(getKeyValue()!=0);//确保单击只输入一个值
result=0; //运算结果清零
Count1=0; //计次1清零
temp1=0; //中间值1清零
temp2=0; //中间值2清零
OLED_ShowString(58,4," CANCEL "); //更新显示
delay_cycles(16000000);//延时500ms
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
OLED_ShowFloatNum(58,4,duty1,3,4,16); //更新显示
return;
}
}
}
return;
}
void changefreq1(void)//具体调频率
{
//int temp2=key_value2;
int Count = 0;
uint32_t temp1=0;
double temp2=0;
int key_value2 = 0;
while(1){
key_value2 = getKeyValue();
if(key_value2 != 0)
{
delay_cycles(640000);
if(key_value2<=10) //如果S1~S10按键按下,输入值
{
while(getKeyValue()!=0);//确保单击只输入一个值
if(Count<7) //如果输入次数小于7
{
temp1*=10; //总输入值左移一位
temp1+=key_value2%10; //获取一位输入
Count++; //计次加一
}
delay_cycles(640000);
OLED_ShowNum(42,2,temp1,8,16); //更新显示
}
if(key_value2==16) //如果S16按键按下,确认
{
while(getKeyValue()!=0);//确保单击只输入一个值
if(temp1<=1000000) //如果频率小于等于1MHz
{
ARR1=DL_Timer_getLoadValue(PWM_1_INST)+1;
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
freq1=temp1;
ARR1=MYCLK/freq1+1;
CCR1=MYCLK/freq1*duty1/100;//调整频率的同时,尽量保证占空比不发生变化
DL_TimerG_setCaptureCompareValue(PWM_1_INST,CCR1,GPIO_PWM_1_C0_IDX);//设置捕获比较值
DL_Timer_setLoadValue(PWM_1_INST,ARR1-1);
OLED_ShowNum(42,2,freq1,8,16); //更新显示
ARR1=DL_Timer_getLoadValue(PWM_1_INST)+1;
CCR1=DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
duty1=(double)CCR1/ARR1*100;
OLED_ShowFloatNum(58,4,duty1,3,4,16); //更新显示
temp1=0; //中间值清零
Count=0; //计次清零
return;
}
else if(temp1>1000000) //否则
{
Count=0; //计次清零
temp1=0; //中间值清零
OLED_ShowString(42,2," ERROR "); //更新显示
delay_cycles(16000000);//延时500ms
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
freq1=MYCLK/ARR1;
OLED_ShowNum(42,2,freq1,8,16);
return;
}
}
if(key_value2==15) //如果S15按键按下,取消
{
while(getKeyValue()!=0);//确保单击只输入一个值
Count=0; //计次清零
temp1=0; //中间值清零
OLED_ShowString(42,2," CANCEL "); //更新显示
delay_cycles(16000000);//延时500ms
ARR1=DL_TimerG_getLoadValue(PWM_1_INST)+1;
freq1=MYCLK/ARR1;
OLED_ShowNum(42,2,freq1,8,16);
return;
}
}
}
return;
}
调整通道二同理,只是改了函数名和变量而已,就不列出来了
调整PWM输出状态,通过操作PWM的时钟开关来实现对PWM波输出与否的控制
void StartOrStop1(void)
{
delay_cycles(640000);
if(DL_Timer_isClockEnabled(PWM_1_INST)==false)
{
DL_Timer_enableClock(PWM_1_INST);
OLED_ShowMyCHinese(82, 0, 9);OLED_ShowMyCHinese(98, 0, 10);//9、10开启
delay_cycles(640000);
}
else if(DL_Timer_isClockEnabled(PWM_1_INST)==true)
{
DL_Timer_disableClock(PWM_1_INST);
OLED_ShowMyCHinese(82, 0, 11);OLED_ShowMyCHinese(98, 0, 12);//11、12关闭
delay_cycles(640000);
}
}
5 遇到的主要难题
5.1 定时中断
通过定时中断定时每一秒发送相应信息
void TIMER_0_INST_IRQHandler()
{
uint32_t ch1_ARR = DL_Timer_getLoadValue(PWM_1_INST)+1;
uint32_t ch1_CCR = DL_TimerG_getCaptureCompareValue(PWM_1_INST,GPIO_PWM_1_C0_IDX);
double ch1_duty = (double)ch1_CCR/ch1_ARR*100;
uint32_t ch1_freq = MYCLK/(ch1_ARR-1);
uint32_t ch2_ARR = DL_Timer_getLoadValue(PWM_2_INST)+1;
uint32_t ch2_CCR = DL_TimerG_getCaptureCompareValue(PWM_2_INST,GPIO_PWM_2_C0_IDX);
double ch2_duty = (double)ch2_CCR/ch2_ARR*100;
uint32_t ch2_freq = MYCLK/(ch2_ARR-1);
Serial_SendString("CH1:freq= ");Serial_SendNumber(ch1_freq, 7);Serial_SendString(" Hz ");Serial_SendString("duty= "); Serial_SendFloatNum(ch1_duty, 3, 6);Serial_SendString("%\r\n");
Serial_SendString("CH2:freq= ");Serial_SendNumber(ch2_freq, 7);Serial_SendString(" Hz ");Serial_SendString("duty= "); Serial_SendFloatNum(ch2_duty, 3, 6);Serial_SendString("%\r\n");
}
5.2 串口输出
将每个字节的发送“DL_UART_Main_transmitData(UART_1_INST, Byte); /* 等待数据传输完毕 */while (DL_UART_Main_isBusy(UART_1_INST));”封装进各类字符串和数字发送
//一系列串口函数
void Serial_SendByte(uint8_t Byte)
{
/* 发送一个数据 */
DL_UART_Main_transmitData(UART_1_INST, Byte);
/* 等待数据传输完毕 */
while (DL_UART_Main_isBusy(UART_1_INST));
}
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
void Serial_SendFloatNum(double Number, uint8_t IntLength, uint8_t FraLength)
{
uint32_t PowNum, IntNum, FraNum;
/*提取整数部分和小数部分*/
IntNum = Number; //直接赋值给整型变量,提取整数
Number -= IntNum; //将Number的整数减掉,防止之后将小数乘到整数时因数过大造成错误
PowNum = oled_pow(10, FraLength); //根据指定小数的位数,确定乘数
FraNum = round(Number * PowNum); //将小数乘到整数,同时四舍五入,避免显示误差
IntNum += FraNum / PowNum; //若四舍五入造成了进位,则需要再加给整数
/*显示整数部分*/
Serial_SendNumber(IntNum,IntLength);
/*显示小数点*/
Serial_SendString(".");
/*显示小数部分*/
Serial_SendNumber(FraNum,FraLength);
}
5.3 浮点数输入、传输和输出
浮点输入的重点在于记录小数点的输入时机,从而计算相应数值
浮点传输和输出的重点在于分别输出整数部分、小数点和小数部分
6 未来的计划建议
矩阵键盘和OLED显示目前还有一点小bug,除此之外,调整频率的时候占空比会变化的问题还没有解决,OLED显示不够方便,显示的UI过于简陋,作者正在赶工改善中……