软件版本:keil5.38a,TI sysconfig
硬件配置:MSPL1306开发板
工具:DS1022C示波器、万用表
一:【项目目标】
1:使用MSPM0L1306的定时器,生成2路重复频率1Hz~1MHz的PWM信号,每一路的重复频率和占空比都可独立调节。
PWM重复频率在1Hz~10KHz内连续可调,通过按键切换至100KHz和1MHz。
PWM重复频率越高,占空比分辨率降低(主时钟32MHz为例)
2:PWM重复频率在1Hz~10KHz时,占空比的精度不低于1/1000,100Khz和1Mhz的占空比精度按照表格即可。
使用按键或拨码开关组合调节输出频率、占空比,并由按键控制每一路PWM信号的输出。
能在OLED屏幕上显示基础信息,当前使用引脚示意、引脚相应的PWM参数。
将PWM参数(包括两个通道的频率占空比、及通道工作与否)按照1s左右的周期,通过串口发送到上位机。
二:【设计思路】
需要翻阅电路板的原理图,确定好项目所使用的IO口,功能等基本信息,具体使用的资源如下所示:
1:主芯片使用SPI的通讯方式与OLED屏幕建立连接:在屏幕上面显示一些PWM的参数;
2:按键输入使用矩阵键盘的方式,总共使用8个IO口,完成16个键值的读取(这里我对例程进行一下简单的修改,在按下功能按键时,触发一次,防着其重复触发)
3:定时器1、定时器2做为两路PWM输出时钟来源,在复用引脚时候,需要避开矩阵按键、OLED所使用的io口。
4:定时器0做为系统时钟,完成基本的定时计数功能。
5:串口0重定义一下,利用printf函数输出当前两路PWM的工作状态。
三:【硬件框架】
四:【软件流程图】
4.1:实现思路如下:
1:单片机上电初始化外设,使能外设的时钟。
2:初始化OLED屏幕,开机界面下显示基本信息。
3:在主程序内,需要检测按键键值,然后处理相应的键值。
4:定时器0用作系统定时器,处理刷新界面和串口输出功能。
4.2:IO口所使用资源如下:
1:引脚使用情况
PWM0:使用引脚 PA26
PWM2:使用引脚 PA8
定时器0:作为系统时钟,刷新屏幕、上传数据到串口0;
OLED屏幕: PA2 CS
OLED屏幕: PA4 CS
OLED屏幕: PA5 CS
OLED屏幕: PA6 CS
y轴最大 设置为6: 取值:0 2 4 6
X轴最大 设置为114:取值:0 2 4 6 8个数字
串口使用引脚:
PA10 UART1_Tx
PA11 UART1_Rx
PA19 DAP_SWDIO
PA20 DAP_SWCLK
按键使用引脚记录:
横向:PA0 PA1 PA7PA12
纵向:PA13 PA14 PA17、PA18
五:【部分代码分析】
5.1:OLED屏幕知识:
由于该项目中使用的屏幕并没有字库,如果需要显示汉字的情况下,需要我们自己自行的制作字库。
这里根据频率的分辨率大小,我设置的字库大小为16*16像素的。
Oled知识分享:OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。因为具备轻薄、省电等特性,因此从2003年开始,这种显示设备在MP3播放器上得到了广泛应用,而对于同属数码类产品的DC与手机,此前只是在一些展会上展示过采用OLED屏幕的工程样品。自2007年后,寿命得 到很大提高,具备了许多LCD不可比拟的优势。
GND:电源地VCC:2.2V~5.5V SCL(D0):CLK时钟 (高电平2.2V~5.5V)SDA(D1):MOSI数据(高电平2.2V~5.5V)RST:复位(高电平2.2V~5.5V)D/C:数据/命令(高电平2.2V~5.5V) 兼容3.3V和5V控制芯片的I/O电平(无需任何设置,直接兼容)。
汉字制作范例:
注意:只要制作使用的汉字即可,不要过多的制作字库,那样会由很多的浪费程序的空间。这里我仅仅制作了二十几个汉字,用来显示基本的信息。操作底层代码,这里就不过多的介绍,参考例程即可。屏幕刷新我利用的定时0,每600ms刷新一次,这样从视觉看,看不到跳动,而且为了减轻CPU的刷新负担,将固定的汉字做成静态,仅仅刷新两路占空比和频率的数据。
5.2:PWM输出功能调试过程
PWM(Pulse Width Modulation脉宽调制)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。它是一种对模拟信号电平进行数字编码的方法。是指在一定时间内波形的高电平(即1状态)所占用的时间比例。通过高分辨率计数器的使用,方波占空比被调制用来对一个模拟信号的电平进行编码。PWM信号任然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有,要么完全无。比如我们的电压输出是5V的,那么经过改变PWM的占空比,可以达到在一定时间内输出3.3V或者1.3V的效果。
MSPM0L系列一共有4个定时器,可以分为2种类型,通用计时器(TIMG)和高级控制计时器(TIMA)。而PWM功能就是在定时器的基础上实现的。从用户手册上可以了解到,MSPM0L1306有4个定时器,每一个定时器拥有2个PWM通道,每一个PWM通道都对应单片机的一个管脚,这个引脚不是唯一固定的,可能有一个或者两个管脚都对应同一个通道。比如说TIMG_C0对应PA5和PA12,就是说PA5和PA12管脚都可以配置为定时器的通道0,我们在使用的时候可以任选其一进行配置。
PWM是脉冲宽度调制,具有两个非常重要的参数:频率和占空比。
频率:PWM的频率是整个周期的倒数。
占空比:占空比是指一个周期内高电平所占的比例。
在调试过程中,使用的是ti的配置工具,systconfig,基本的底层实在这个里面完成,使能引脚的时候注意需要避开按键所使用的引脚,要不会导致部分按键失灵。
配置范例如下:
部分代码展示如下:分为更新输出频率函数:输出频率的范围可以从syscofig里面看到,这点是很方便的。
修改频率和占空比方法:
1:确定好定时器的时钟来源,配置定时器的分频和预分频数值,可以看到分频在预分频之后的频率,设置好PWM的周期计数值,就可以得到PWM的频率了。
2:可以通过函数DL_TimerG_setCaptureCompareValue设置定时器PWM的比较值,通过修改比较值看可以实现修改PWM的占空比。
/***********************************************************************************************
* @brief UpdateFrequency
* @param 更新频率函数
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void UpdateFrequency(int32_t vlaue ,int temp)
{
int32_t frequency ;
int duty ;
frequency = 3200000 / vlaue ;
duty = temp /100.0 * frequency ;
DL_TimerG_PWMConfig DL_TimerG_PWMConfig_RC ;
DL_TimerG_PWMConfig_RC.period = frequency ;
DL_TimerG_PWMConfig_RC.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN_UP ;
DL_TimerG_PWMConfig_RC.startTimer = DL_TIMER_START ;
DL_TimerG_initPWMMode(
PWM_0_INST, (DL_TimerG_PWMConfig *) &DL_TimerG_PWMConfig_RC);
DL_TimerG_setCaptureCompareValue(PWM_0_INST, duty, DL_TIMER_CC_0_INDEX);
}
/***********************************************************************************************
* @brief UpdateDutycycle
* @param 更新占空比函数
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void UpdateDutycycle(int vlaue)
{
DL_TimerG_setCaptureCompareValue(PWM_0_INST, vlaue, DL_TIMER_CC_0_INDEX);
}
/***********************************************************************************************
* @brief StartOutputPWM
* @param 开始输出PWM
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void StartOutputPWM(void)
{
DL_TimerG_PWMConfig DL_TimerG_PWMConfig_RC ;
DL_TimerG_PWMConfig_RC.startTimer = DL_TIMER_START ;
DL_TimerG_initPWMMode( PWM_0_INST, (DL_TimerG_PWMConfig *) &DL_TimerG_PWMConfig_RC)
}
/***********************************************************************************************
* @brief StopOutputPWM
* @param 停止输出PWM
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void StopOutputPWM(void)
{
DL_TimerG_PWMConfig DL_TimerG_PWMConfig_RC ;
DL_TimerG_PWMConfig_RC.startTimer = DL_TIMER_STOP ;
DL_TimerG_initPWMMode( PWM_0_INST, (DL_TimerG_PWMConfig *) &DL_TimerG_PWMConfig_RC);
}
/***********************************************************************************************
* @brief void FreAutoOutputPWM1(void)
* @param 自动改变波特率函数
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void FreAutoOutputPWM1(void)
{
iFrequencyNO1 =iFrequencyNO1 +500 ;
if(iFrequencyNO1 >=100000) iFrequencyNO1 = 1000 ;
UpdateFrequency(iFrequencyNO1 ,cDutyCycleNO1);
}
/***********************************************************************************************
* @brief void DutyAutoOutputPWM1(void)
* @param 自动改变占空比函数
* @retval 无
* @author 聪聪哥哥
* @version V1.1.0
* @date 27-5-2024
*************************************************************************************************/
void DutyAutoOutputPWM1(void)
{
cDutyCycleNO1 =cDutyCycleNO1 +5;
if(cDutyCycleNO1 >=100) cDutyCycleNO1 = 5 ;
UpdateFrequency(iFrequencyNO1 ,cDutyCycleNO1);
}
5.3: 定时器配置过程:
定时器介绍:单片机的定时功能是通过计数来实现的,当单片机每一个机器周期产生一个脉冲时,计数器就加一。定时器的主要功能是用来计时,时间到达之后可以产生中断,提醒计时时间到,然后可以在中断函数中去执行功能。
ti配置如下:
定时器0中断处理部分:
void TIMER_0_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST))
{
case DL_TIMER_IIDX_ZERO:
{
uiSampleCount++ ;
if(uiSampleCount %2 == 0)
{
b10MS = 1 ;
}
if(uiSampleCount %5 == 0)
{
}
if(uiSampleCount %6 == 0)
{
DisplayPage(DisMode);
}
if(uiSampleCount>=10)
{ uiSampleCount = 0 ;
b1000MS = 1 ; } }break;
default:
break;
}
}
此处使用定时器的主要实现了:600ms时间中断刷新屏幕显示 和1S中断上传程序的状态信息。
5.4:串口的调试:
串口通讯介绍:
串口是指外设和处理器之间通过数据信号线、地线和控制线等,按位进行传输数据的一种通讯方式。尽管传输速度比并行传输低。但串口可以在使用一根线发送数据的同时用另一根线接收数据。
串口通信参数包括波特率(Baud Rate)、数据位(Data Bits)、校验位(Parity Bits)、停止位(Stop Bits)等
开发板使用的是MSPM0L1306为主控,它只有两个串口可用,分别是UART0和UART1。
需要注意的是,不是所有的引脚都支持串口功能,只有特定的引脚才支持。这个需要大家去根据数据手册进行查找。
本案例将使用PA10和PA11引脚上的UART0外设,搭配开发板板载的DAP仿真器的串口接口,此时注意仅仅适用于有串口通讯的DAP,实现接收上位机(电脑)发送过来的串口数据,再通过串口发送功能将数据发送给上位机(电脑)的操作。不过要注意一下需要重定义一下printf函数
//===================对printf进行重定向===================
#pragma(__use_no_semihosting)
struct FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
/* 串口打印函数代码:*/
if(OutputFlag == 1)
printf("CH1 State: open\r\n" ) ;
else printf("CH1 State: close\r\n" ) ;
printf("CH1 cDutyCycleNO1: = %3d \t iFrequencyNO1 = %3d\r\n", cDutyCycleNO1, iFrequencyNO1);
/*通道2占空比、频率输出信息*/
if(OutSecondputFlag == 1)
printf("\r\nCH2 State: open" ) ;
else printf("\r\nCH2 State: close" ) ;
printf("\r\nCH1 cDutyCycleNO2: = %3d \t iFrequencyNO2 = %3d\r\n", cDutyCycleNO2, iFrequencyNO2);
}
实物测试图片:
六:【调试代码中遇到问题及其解决办法】
1:如果在下载时出现下图现象,说明当前代码内存过大,要减少ram内存,要么在.s文件中加大堆的空间,要么关掉keil中的微库,要么将优化功能开到最大,优化一下代码的内存空间
2:代码时不时的就会进入系统的延时函数里面,解决办法:刷新屏幕的时间加长,减少代码的执行量。
3:在调试串口输出的时候,printf重定义有问题不能正常输出,解决办法:查看串口定义引脚、打开miclib库功能。
4:调试PWM输出功能时候,开始的时候没有注意与矩阵矩阵按键所使用的IO口冲突,导致按键失灵,更改IO空后解决。
七:【未来计划】
1:该项目工程已经实现了两路PWM输出、OLED显示、按键检测和串口输出的功能,但是目前还有存在优化的空间,后期计划将PWM参数存储到板载的flash里面,这样下次在使用的时候就无需重新配置。
2:板载的陀螺仪模块目前还没有调试通,稍后调试一下,做成检测水平仪的装置。做好后在上传项目资料。
3:板载的IIC存储模块功能没有进行调试,后期在进行调试,学习。
4:板载的wifi模块接口没有移植,稍后先移植无线模块功能,性能稳定的话,利用主芯片制作一个无线通讯模块,这样板子会比脚小巧。