项目介绍
使用MSPM0L1306的定时器,生成2路重复频率1Hz~1MHz的PWM信号,每一路的重复频率和占空比都可独立调节。
其中,PWM重复频率在1Hz~10KHz内连续可调,通过按键切换至100KHz和1MHz。
PWM重复频率越高,占空比分辨率降低
设计思路
移植RT-Thread,在RTOS环境下更方便的编写可移植代码,为后续功能打框架基础
使用两路独立的定时器TIMG2生成PWM0,输出引脚PA3;TIMG1生成PWM1,输出引脚PA14
通过串口实时汇报数据
硬件框图和软件流程图
系统启动流程图
主任务流程图
简单的硬件介绍
准备一条USB转串口线,一条USB TYPEC,一台示波器(我使用的是正点原子便携示波器)
UART1
开发板底板自带调试器和UART,UART接到了MSPM0L1306的PA10和PA11(UART1)。我们可以直接和调试器的UART来和单片机的UART1进行通信。
UART0
连接PA8 PA9,此串口和上位机进行通信。
PWM
两路PWM输出,使用两个独立的定时器:TIMG2生成PWM0,输出引脚PA3;TIMG1生成PWM1,输出引脚PA14
实现的功能及图片展示
指令操作
示波器截图
主要代码片段及说明
为了实现PWM重复频率在1Hz~10KHz内连续可调,写了一个函数,这个函数对于32MHz的CortexM0内核计算量有点大,在有些频率下需要计算的时间有点长。简单的优化了一下,使其更适合计算1-10000Hz的PWM参数计算。
/* 生成特定PWM频率的CPS和LOAD值 */
static int mspm0l1306_pwm_gen(uint32_t freq, uint8_t *cps, uint16_t *period) {
if (!cps || !period)
return -1;
/* CPS:8bit, LOAD:16bit */
for (uint8_t i = 0; i < 0xFF; i++) {
for (uint32_t j = 0xFFFF; j > 1000; j--) {
uint32_t f = CPUCLK_FREQ / ((i + 1) * j);
if (f == freq || f - freq < freq * 0.01) {
*cps = i;
*period = j;
return 0;
}
}
}
return -1;
}
PWM控制原理
为了更灵活的调节pwm,使用 rtthread msh 组件实现了一个pwm指令
MSH_CMD_EXPORT(pwm, pwm ctrl: pwm <chan> <freq|duty> [value]);
测试示例
pwm <chan> <freq|duty> [value]
# 例如调节PWM通道1输出频率为2000Hz
pwm 1 freq 2000
# 调节pwm通道1占空比为60%
pwm 1 duty 60
PWM调节原理
我们来看下本次任务的要点
- 使用MSPM0L1306的定时器,生成2路重复频率1Hz~1MHz的PWM信号,每一路的重复频率和占空比都可独立调节。
- 其中,PWM重复频率在1Hz~10KHz内连续可调,通过按键切换至100KHz和1MHz。
- PWM重复频率越高,占空比分辨率降低(主时钟32MHz为例)
通过MSPM0L1306参考手册,我们知道PWM输出需要配置定时器的Compare Mode
为了生成两路独立的PWM波,需要使用两个独立的普通定时器TIMG
从下图可以看出LOAD寄存器和PWM频率相关,一个PWM周期=LOAD CNT*定时器周期。占空比和CC寄存器有关系。CC寄存器配置的数值不能超过LOAD寄存器
LOAD CNT越大,周期越长(频率越低),调节精度(CC)也相应增加。反之,LOAD CNT越小,PWM波周期越小(频率越高),调节精度越小。
上位机通信
上位机主要用来实时显示运行参数,包括当前的ADC值,每个pwm通道的频率和占空比。
UART通信协议:每包固定15个字节。UART通信参数:115200, 8N1。
| 1 | 2 | 3-6 | 7-10 | 11-14 | 15 |
|---|---|---|---|---|---|
| 帧头 | 1字节 | 无符号整型 | 无符号整型 | 无符号整型 | 帧尾 |
| 0x3E | 通道 | ADC实时采样 | PWM频率 | PWM占空比 | 0xAA |
无符号整型是使用小端传输。
下位机在状态发生改变时主动汇报数据,上位机可以实时显示当前的ADC值,PWM通道,PWM频率和PWM占空比。
实际的例子
3c012f0d00008813000043000000aa遇到的主要难题及解决方法
本次主要的难点是移植RT-Thread nano版本,因为MSPM0L1306的SRAM比较小(4KB),需要进行适当的裁剪。
1. 添加rtthread
为了更好的了解OS移植细节,这里使用手动方式添加
rtthread
- 从
rtthread官网下载3.1.5 nano版本。 - 将
rt-thread文件夹整体拷贝到工程中,删除libcpu文件夹下其他架构代码,只保留arm目录下的common和cortex-m0文件夹。 - 添加文件到MDK工程中。为了方便管理这里新建了
rtthread-bsprtthread-componentsrtthread-srcrtthread-libcpu4个组,按照对应关系将源码加到MDK工程中。需要我们修改的代码都在rtthread-bsp中。borad.c为初始化入口,在rt_hw_board_init中初始化硬件,打开调试串口。
2. 调整stack
启动文件 startup_mspm0l1306_uvision.s 中默认定义的 0x100(256)太小,需要改成 0x200(512)。否则rtt初始化时会出现栈溢出,导致系统崩溃。
Stack_Size EQU 0x00000200
3. 添加rtthread组件
为了方便调试,添加了 RT_USING_CONSOLE 和 RT_USING_FINSH
MSPM0L1306共有两个串口,UART0作为和上位机通信接口,UART1作为调试串口。
rtconfig.h 配置项如下
#define RT_THREAD_PRIORITY_MAX 8
#define RT_TICK_PER_SECOND 1000
#define RT_ALIGN_SIZE 4
#define RT_NAME_MAX 8
#define RT_USING_COMPONENTS_INIT
#define RT_USING_USER_MAIN
#define RT_MAIN_THREAD_STACK_SIZE 512
#define RT_DEBUG_INIT 0
#define RT_USING_HOOK
#define RT_USING_IDLE_HOOK
#define RT_USING_TIMER_SOFT 1
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 200
#define RT_USING_SEMAPHORE
#define RT_USING_MAILBOX
#define RT_USING_HEAP
#define RT_USING_SMALL_MEM
#define RT_USING_TINY_SIZE
#define RT_USING_CONSOLE
#define RT_CONSOLEBUF_SIZE 128
/* FINSH */
#define RT_USING_FINSH
#define FINSH_USING_MSH
#define FINSH_USING_MSH_ONLY
#define FINSH_THREAD_PRIORITY 6
#define FINSH_THREAD_STACK_SIZE 512
#define FINSH_HISTORY_LINES 1
#define FINSH_USING_SYMTAB
4. UART1数据接收处理流程
使用UART1 作为 rtthread 调试串口,在 rtthread nano 中,我们只需要实现两个函数 char rt_hw_console_getchar(void) 和 void rt_hw_console_output(const char *str),剩下的事情框架已经帮我们处理好。
这里采用中断接收数据,使用 ringbuffer 和信号量 shell_rx_sem,提高数据处理效率。如果没有接收到串口数据,接收线程会一直被信号量阻塞rt_sem_take,这样可以释放CPU处理其他事情。
char rt_hw_console_getchar(void)
{
char ch = 0;
while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1) {
rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
}
return ch;
}
接收到数据存放到 uart_rxcb ringbuffer中,然后释放 shell_rx_sem 信号量,通知接收线程开始处理数据。
void UART1_IRQHandler()
{
rt_interrupt_enter();
switch (DL_UART_Main_getPendingInterrupt(UART1)) {
case DL_UART_MAIN_IIDX_RX:
{
uint8_t data = DL_UART_Main_receiveData(UART1);
rt_ringbuffer_putchar(&uart_rxcb, data);
rt_sem_release(&shell_rx_sem);
}
break;
default:
break;
}
rt_interrupt_leave();
}
5. OS资源占用
rtthread nano 资源占用低,MSPMOL1306这么小的资源(64KB Flash,4KB SRAM)上面都能正常运行,不得不说rtthread nano优化的还不错。
对比了一下移植前面的资源占用
# 裸机
Program Size: Code=3352 RO-data=1800 RW-data=0 ZI-data=352
# 使用上面 rtconfig.h 定义的配置项资源占用情况
Program Size: Code=19166 RO-data=2610 RW-data=64 ZI-data=1800
运行状态中的 rtthread nano
msh >ps
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 6 ready 0x00000094 0x00000200 82% 0x00000005 000
tidle 7 ready 0x00000040 0x00000100 31% 0x0000000b 000
timer 4 suspend 0x00000050 0x000000c8 40% 0x0000000a 000
main 2 suspend 0x00000080 0x00000200 31% 0x0000000f 000
msh >free
total memory: 2208
used memory : 1444
maximum allocated memory: 1444
未来的计划或建议
经过这个项目的实践,对MSPM0L1306有了更深入的了解。这款芯片主打低功耗,芯片虽小,但是功能强大,能应用到相对复杂的项目中。
希望后面有项目机会可以用起来~