目录
绪论
随着科技的飞速发展,数字信号处理器(DSP)在现代电子系统中扮演着越来越重要的角色。特别是在工业自动化、电机控制、通信等领域,DSP以其高速的数据处理能力、强大的算法执行效率和灵活的编程方式,成为不可或缺的核心部件。德州仪器(TI)的TMS320F28335芯片作为一款高性能的DSP芯片,其丰富的外设接口和强大的处理能力使得它在各种应用中备受青睐。
在前期的研究中,我们已经针对TMS320F28335芯片进行了深入探索,包括GPIO控制LED及蜂鸣器实验、定时器的使用等基础功能模块的实验。然而,由于时间和外在计划限制,我们未能针对其他功能模块(如串口通信、按键控制、直流电机驱动、液晶屏显示等)进行详尽的阐述和实验展示。尽管如此,这些功能模块在DSP系统中的应用同样重要,它们共同构成了DSP系统的完整性和功能性。
因此,在本综合项目中,我们将着重探索并整合这些功能模块,以构建一个功能完备、交互性强的DSP系统。具体来说,本项目将涉及以下几个方面的研究和实验:
-
串口通信:我们将深入研究串口通信的原理和实现方式,通过串口实现DSP与外部设备之间的数据传输和信息交互。这将为系统的远程监控和控制提供可能。
-
按键控制:通过设计和实现按键控制逻辑,使得用户能够直观地通过按键来操作系统。这不仅可以增强用户体验,还能为系统提供多样化的操作方式。
-
直流电机驱动:我们将探讨如何精确控制直流电机的运行状态,包括速度、方向等。这将为电机控制应用提供有力的技术支持,并推动DSP在工业自动化领域的应用。
-
液晶屏显示:通过液晶屏实时显示系统状态,为用户提供一个直观、友好的操作界面。这将有助于用户更好地了解系统的工作状态和运行情况。
通过本项目的实施,我们不仅能够弥补前期研究中未涉及的功能模块,还能更全面地理解DSP的工作原理和应用技巧。此外,本项目的成果也将为后续更复杂、更高级的DSP应用和开发提供宝贵的经验和参考。
我们相信,通过不懈的努力和探索,我们一定能够成功实现本项目的目标,为DSP技术的发展和应用贡献自己的力量。在接下来的章节中,我们将详细介绍项目的具体实施方案、技术难点以及预期的研究成果。
串口与按键控制直流电机运行及液晶屏显示状态
一、实验目的及要求
本项目旨在通过DSP TMS320F28335数字信号处理器,实现串口通信与按键控制直流电机的运行,并通过液晶屏实时显示电机的运行状态。本项目不仅锻炼了我们对DSP TMS320F28335芯片编程和应用的能力,也加深了对电机控制原理、串口通信协议以及液晶屏显示技术的理解。
二、实验原理
DSP28335是一款由德州仪器(Texas Instruments)生产的数字信号处理器(DSP),它内部集成了多个高级外设,包括增强型脉冲宽度调制(ePWM)模块等。增强型脉冲宽度调制(ePWM)模块。利用这些ePWM模块,可以实现对直流电机的精确控制。
1、PWM信号生成:ePWM模块能够产生周期性的PWM信号,这些信号的频率和占空比可以根据需要进行调整。调整PWM信号的频率和占空比,可以精确控制直流电机的速度和方向。
2、时基(TB)模块:负责产生PWM信号的周期和脉宽。通过配置时基周期寄存器(TBPRD)和计数器的模式(如增计数、减计数或增减计数),可以精确地控制PWM信号的周期和起始点。。
3、计数比较(CC)模块:该模块比较时基计数器的值与预设的比较寄存器(CMPA和CMPB)中的值。根据比较结果,CC模块决定PWM信号输出的高低电平状态。
4、动作定则(AQ)模块:当特定事件(如计数器达到PRD或ZERO值)发生时,AQ模块负责设置PWM输出(ePWMxA和ePWMxB)的状态。
这允许用户根据需要对PWM信号进行更复杂的控制。。
5、死区(DB)模块:为了防止两个PWM输出同时导通,DB模块在PWM信号的上升沿和下降沿之间引入一个“死区”时间。这对于电机驱动电路的保护至关重要,可以防止潜在的损坏。
6、PWM斩波(PC)模块:PC模块在PWM信号上叠加一个小的脉冲,以减少电机驱动中的开关损耗。通过这种方式,可以提高电机的驱动效率。
7、错误联防(TZ)模块:TZ模块检测可能的错误情况,并在检测到错误时立即将PWM输出(ePWMxA和ePWMxB)设置为预定义的值。这有助于保护电机和驱动电路免受过流、过热等潜在损坏。
8、事件触发(ET)模块:ET模块允许用户定义和触发特定的事件,如中断。这些事件可以用于同步多个ePWM模块、与其他处理器事件同步或执行其他复杂的控制任务。
9、PWM信号的应用:PWM信号通过DSP28335的GPIO引脚输出,连接到电机驱动电路,如图 1,从而控制电机的转速和方向。
按键模块。通过按键输入,实现功能选择和本地对直流电机的控制。如图 2,使用杜邦线分别连接U17的1与J14的2、U18的1与J14的3。以GPIO13为例,当SW3按键按下时,GPIO13由高电平变为低电平,通过程序扫描GPIO13检测低电平持续时间来判断是短按还是长按还是按特定的时间的按键状态,或者是通过外部中断来检测边沿触发的情况。
蜂鸣器模块。如图 3,GPIO53 管脚通过插针 J15 与三级管链接控制蜂鸣器。如果想让蜂鸣器响则需要控制 GPIO53 管脚为输出并且是低电平(三极管的集电极和射
极导通有电流功能蜂鸣器)。否则需要控制 GPIO53 管脚为输出并且是高电平。项目中没有提及到蜂鸣器,这里采用蜂鸣器与按键进行配合,给操作者发出警报和提醒的声音。
LCD12864液晶屏显示模块。如图 4,GPIO60--->对应的是LCD的RS、GPIO61--->对应的是 Lcd12864 的EN,LCD12864 的RW信号被固定为低电平,只向 LCD12864 写数据,而不读数据。用于实时显示直流电机的运行状态,包括运行时间、转速、转向等信息。
串口通信模块。如图 5,从图中得知 GPIO62 和 GPIO63 对应的是串口 C,其余两个 IO 对应的是串口B。数据发送和接收线:逻辑1(MARK)的电平范围是-3V~-15V。
逻辑0(SPACE)的电平范围是+3V~+15V。
采用标准的RS-232接口,实现与上位机的通信,接收来自上位机的控制指令。
通过上述原理,DSP28335可以有效地控制直流电机,实现各种应用中的精确速度和位置控制。
上面图 6,为开发程序的流程图。
三、实验软硬件环境
- 计算机(已安装 CCSv6.0 开发环境)
- SXD28335 或 SXD28335B 开发板
- 5V 2A (或 3A)DC 电源
- USB 转串口线一条
- 仿真器(本手册都是以三兄弟嵌入式生产的 XDS100V3 仿真器为例,其余仿真器类似)
直流电机一个
四、实验过程
1、确认实验所用的硬件设备(直流电机、驱动电路、单片机、液晶显示屏、按键、串口通信接口等)是否齐全且功能正常。检查并准备好所有必要的软件工具(编程环境、编译器、调试工具等)。
2、硬件连接。将直流电机连接到电机驱动电路;将驱动电路的控制端连接到DSP TMS320F28335的I/O端口;将液晶显示屏连接到DSP TMS320F28335的LCD接口;将按键连接到DSP TMS320F28335的I/O端口,并确保它们能够被正确检测。
3、软件编程。初始化DSP TMS320F28335的I/O端口和串口通信;编写电机控制程序,实现电机的启动、停止、正反转和调速功能;编写串口通信程序,实现与上位机的通信;编写LCD显示程序,用于在LCD上显示电机状态;编写按键处理程序,用于检测按键输入并执行相应操作。
4、测试与调试。运行电机控制程序,测试电机的启动、停止、正反转和调速功能是否正常。
运行串口通信程序,测试是否能够接收和发送正确的指令。运行LCD显示程序,确保电机状态能够正确显示在LCD上。运行按键处理程序,确保按键能够正确控制电机的运行状态。
5、系统集成。将所有功能模块集成到一个系统中,确保它们能够协同工作。编写主程序,实现按键控制和串口控制的逻辑切换。
6、实验运行。运行程序,通过按键或串口指令控制电机运行。观察LCD显示屏,确认电机状态的显示是否正确。
五、测试/调试及实验结果分析
1、串口通信协议
(1)设置脉冲和方向:0xAA 0x01 <脉冲宽度(2字节)> <方向(1字节:0x00为正向,0x01为反向)> <蜂鸣器(2字节)> <校验和> 0xBB
例如(Hex):AA 01 09 C4 01 13 88 6A BB 设置直流电机正转且脉冲宽度为2500,蜂鸣器响5000ms;
(2)获取状态:0xAA 0x02 <校验和> 0xBB例如(Hex):AA 02 02 BB
响应:0xAA 0x82 <运行时间(2字节)> <当前脉冲宽度(2字节)><当前方向(1字节)> <校验和> 0xBB
例如(Hex):AA 82 00 0A 09 C4 01 A6 BB
解析00 0A->直流电机运行时间10s;09 C4->当前脉冲宽度为2500;01-> 当前方向;
(3)<校验和>=除了启动字符(0xAA)<校验和>前面的所有之和取第八位。
2、实验结果
(1)按下SW5,系统由待机进入工作状态,LCD12864显示按键功能;再次按下SW5,进入电机运行设置界面;短按SW5,切换直流电机运行方向;长按SW5,系统进入待机状态。
(2)SW3,只有系统进入工作状态才能有效,否则,无论如何按都无效;SW3的功能有菜单切换和增加脉冲宽度(加速)。
(3)SW4,只有系统进入工作状态才能有效,否则,无论如何按都无效;SW3的功能有菜单切换和减小脉冲宽度(减速)。
(4)通过串口助手发送(HEX):AA 01 09 C4 01 13 88 6A BB;电机运转;显示屏显示电机运行状态,其脉宽显示为2500;蜂鸣器持续响5s。
3、代码分析
DSP2833x_main.c 文件:
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP2833x Examples Include File
#include "LCD12864.h"
#include "Buzzer.h"
#include "Key.h"
#include "DC_Motor.h"
#include "Serial_Port_Communication.h"
//时钟片标志
Uint16 Time_1ms_flag; //1ms
Uint16 Time_10ms_flag; //10ms
Uint16 Time_100ms_flag; //100ms
Uint16 Time_1000ms_flag; //1000ms
//时钟片计数变量
Uint16 Time_10ms_Count; //用于生成10ms标志
Uint16 Time_100ms_Count; //用于生成100ms标志
Uint16 Time_1000ms_Count; //用于生成1000ms标志
#define SCI_C_RXST_R (*((volatile unsigned long *)0x0000))
#define SCI_C_RIS_R (*((volatile unsigned long *)0x0000))
#define SCI_RIS_RX (0x02)
void SCI_C_StrRecv(char *str, unsigned int maxLen);
#define SCI_C_RX_BUFFER_SIZE 256 // 接收缓冲区大小
#define SCI_C_TIMEOUT 1000 // 超时时间(例如,以微秒为单位)
volatile unsigned char sci_c_rx_buffer[SCI_C_RX_BUFFER_SIZE]; // 接收缓冲区
volatile unsigned char sci_c_rx_index = 0; // 缓冲区索引
volatile unsigned char sci_c_rx_overflow = 0; // 缓冲区溢出标志
//串口中断接收函数初始化
__interrupt void sci_c_RX_isr(void);
__interrupt void sci_c_TX_isr(void);
int receive_string_sci_c(char *dest, int max_len) ;
int receive_string_sci_c2(uchar *dest);
//定时器中断函数初始化
__interrupt void cpu_timer0_isr(void);
__interrupt void cpu_timer1_isr(void);
__interrupt void cpu_timer2_isr(void);
//IO口初始化
void LED_BUZZ_GPIO_Init(void);
// Global counts used in this example
Uint16 LoopCount;
Uint16 ErrorCount;
void GPIO_Init();
int len;
Uint16 p, x,y;
//unsigned Uint16 B[]={0xb3,0x34};
void main(void)
{
//Uint16 ReceivedChar;
char *msg;
// char msg1[20];
// static unsigned char _10msStep = 0;
// Step 1. Initialize System Control:
// PLL, WatchDog, enable Peripheral Clocks
// This example function is found in the DSP2833x_SysCtrl.c file.
InitSysCtrl();
InitXintf();
// Step 2. Initalize GPIO:
// This example function is found in the DSP2833x_Gpio.c file and
// illustrates how to set the GPIO to it's default state.
// InitGpio(); // Skipped for this example
InitXintf16Gpio(); //zq
#if _SCI_C
InitScicGpio();
#endif
#if _SCI_B
InitScibGpio();
#endif
GPIO_Init();
// Step 3. Clear all interrupts and initialize PIE vector table:
// Disable CPU interrupts
DINT;
// Initialize the PIE control registers to their default state.
// The default state is all PIE interrupts disabled and flags
// are cleared.
// This function is found in the DSP2833x_PieCtrl.c file.
InitPieCtrl();
// Disable CPU interrupts and clear all CPU interrupt flags:
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
// EALLOW; // This is needed to write to EALLOW protected registers
// PieVectTable.SCIRXINTC = &scicRxFifoIsr;
// PieVectTable.SCITXINTC = &scicTxFifoIsr;
// EDIS; // This is needed to disable write to EALLOW protected registers
//变量赋初始值
LoopCount = 0;
ErrorCount = 0;
Time_1ms_flag=0;
Time_10ms_flag=0;
Time_100ms_flag=0;
Time_1000ms_flag=0;
g_WeighSetDelayTime = 60;
Str_Sys.FactoryCheckKeyTime = 30;
Str_Sys.State = Sys_Standby;
Str_Sys.b_LCD12864_Init_flag=0;
Str_Sys.b_Sys_PreFactory=0;
Str_Sys.b_DC_Motor_Direction_Init=0;
//管脚初始化
LED_BUZZ_GPIO_Init();
Key_Gpio_Init();
定时器配置start///
EALLOW; // This is needed to write to EALLOW protected registers
PieVectTable.TINT0 = &cpu_timer0_isr;
PieVectTable.XINT13 = &cpu_timer1_isr;
PieVectTable.TINT2 = &cpu_timer2_isr;
EDIS; // This is needed to disable write to EALLOW protected registers
// Initialize the Device Peripheral. This function can be
// found in DSP2833x_CpuTimers.c
InitCpuTimers(); // For this example, only initialize the Cpu Timers
#if (CPU_FRQ_150MHZ)
// Configure CPU-Timer 0, 1, and 2 to interrupt every second:
// 150MHz CPU Freq, 1 second Period (in uSeconds)
ConfigCpuTimer(&CpuTimer0, 150, 1000);
ConfigCpuTimer(&CpuTimer1, 150, 1000000);
ConfigCpuTimer(&CpuTimer2, 150, 1000000);
#endif
#if (CPU_FRQ_100MHZ)
// Configure CPU-Timer 0, 1, and 2 to interrupt every second:
// 100MHz CPU Freq, 1 second Period (in uSeconds)
ConfigCpuTimer(&CpuTimer0, 100, 10000);
ConfigCpuTimer(&CpuTimer1, 100, 1000000);
ConfigCpuTimer(&CpuTimer2, 100, 1000000);
#endif
// To ensure precise timing, use write-only instructions to write to the entire register. Therefore, if any
// of the configuration bits are changed in ConfigCpuTimer and InitCpuTimers (in DSP2833x_CpuTimers.h), the
// below settings must also be updated.
CpuTimer0Regs.TCR.all = 0x4000; // Use write-only instruction to set TSS bit = 0
CpuTimer1Regs.TCR.all = 0x4000; // Use write-only instruction to set TSS bit = 0
CpuTimer2Regs.TCR.all = 0x4000; // Use write-only instruction to set TSS bit = 0
// User specific code, enable interrupts:
// Enable CPU int1 which is connected to CPU-Timer 0, CPU int13
// which is connected to CPU-Timer 1, and CPU int 14, which is connected
// to CPU-Timer 2:
IER |= M_INT1;
IER |= M_INT13;
IER |= M_INT14;
// Enable TINT0 in the PIE: Group 1 interrupt 7
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
定时器配置end///
//SCI配置过程(中断方式)start//
//SCI配置过程(中断方式)
//step1:开启SCI时钟使能
EALLOW;
SysCtrlRegs.PCLKCR0.bit.SCICENCLK = 1; // SCI-C
EDIS;
//step2:引脚初始化
InitScicGpio();
//step3:FIFO初始化
scic_fifo_init(); // Initialize the SCI FIFO
//step4:协议初始化
scic_agreement_init(); // Initalize SCI for echoback
//step5:取中断函数地址
EALLOW;
PieVectTable.SCIRXINTC = &sci_c_RX_isr;
PieVectTable.SCITXINTC = &sci_c_TX_isr;
EDIS;
//step6:开启SCI中断方式
scic_init();
//step7:开启对应外部中断使能
// Enable interrupts
PieCtrlRegs.PIEIER8.bit.INTx5=1; // Enable all SCIA RXINT interrupt
PieCtrlRegs.PIEIER8.bit.INTx6=1; // Enable all SCIA RXINT interrupt
IER |= M_INT8; // enable PIEIER9, and INT9
EINT;
//SCI配置过程(中断方式)end//
// ScicRegs.SCIHBAUD =0x0001; // 9600 baud @LSPCLK = 37.5MHz.
// ScicRegs.SCILBAUD =0x00E7;
sci_c_rx_index=0;
msg = "\r\n\n\nHello World!\0";
scic_msg(msg);
msg = "\r\nYou will enter a character, and the DSP will echo it back! \n\0";
scic_msg(msg);
// InitPieVectTable();
configtestled();
RS=LOW;
delay(5);
RW=LOW;
delay(5);
EN=LOW;
LcdInit();
RS=LOW;
delay(5);
RW=LOW;
delay(5);
EN=LOW;
LcdInit();
delay(5);
WriteCmd12864(CLEAR_SCREEN);
delay(50);
WriteCmd12864(0x80);
delay(300);
DisplayCgrom(A);
delay(80);
WriteCmd12864(0x90);
delay(80);
DisplayCgrom(B);
delay(80);
WriteCmd12864(0x88);
delay(80);
DisplayCgrom(C);
delay(80);
WriteCmd12864(0x98);
delay(80);
DisplayCgrom(D);
// Buzzer_buzz_time_ms(500);
while(1)
{
Dc_motor_control_Init();
LCD12864_Control_Drive();
if(Time_1ms_flag) //1ms
{
Time_1ms_flag=0;
}
if(Time_10ms_flag) //10ms
{
Time_10ms_flag=0;
KeyScan();
KeyPro();
rxIndex_Timeout_Clear();
}
if(Time_100ms_flag) //100ms
{
Time_100ms_flag=0;
}
if(Time_1000ms_flag) //1000ms
{
Time_1000ms_flag=0;
if(Str_Sys.FactoryCheckKeyTime)Str_Sys.FactoryCheckKeyTime--;
//GpioDataRegs.GPATOGGLE.bit.GPIO0=1;
//GpioDataRegs.GPBTOGGLE.bit.GPIO53=1;
//Buzzer_buzz_time_ms(500);
if(temp>0)
{
DC_Motor_running_time++;
LCD12864_Task=LCD_Task7; //更新电机运行时间
}
}
}
}
//UART_ReceiveISR();
__interrupt void sci_c_RX_isr(void)
{
//PieCtrlRegs.PIEACK.bit.ACK8 = 1;
// 检查接收中断标志
if (ScicRegs.SCIRXST.bit.RXRDY)
{
// 读取接收到的数据
UART_ReceiveISR();
}
ScicRegs.SCIFFRX.bit.RXFFINTCLR=1; // 清接收中断标志
PieCtrlRegs.PIEACK.all |=PIEACK_GROUP8; //Issue PIE ACK
}
__interrupt void sci_c_TX_isr(void)
{
ScicRegs.SCIFFTX.bit.TXFFIENA=1;
ScicRegs.SCIFFTX.bit.TXFFINTCLR =1; //Clear Interrupt flag
PieCtrlRegs.PIEACK.all |=PIEACK_GROUP8; //Issue PIE ACK
}
int receive_string_sci_c(char *dest, int max_len)
{
int len = 0;
// 等待数据或超时
// 这里需要实现一个等待机制,例如使用延时或轮询状态标志
// 从缓冲区读取数据到目标字符串
while (sci_c_rx_index > 0 && len < max_len)
{
//dest[len++] = sci_c_rx_buffer[--sci_c_rx_index];
dest[sci_c_rx_index] = sci_c_rx_buffer[--sci_c_rx_index];
len++;
}
// 清除溢出标志(如果需要)
if (sci_c_rx_overflow)
{
sci_c_rx_overflow = 0;
// ... 处理溢出情况 ...
}
return len; // 返回接收到的字符串长度
}
int receive_string_sci_c2(uchar *dest)
{
int len = 0;
Uint16 Temp_index=0;
if(sci_c_rx_index>0)
{
Temp_index=sci_c_rx_index;
}
while (Temp_index>0 && (sci_c_rx_buffer[Temp_index-1]!='\r') && (sci_c_rx_buffer[Temp_index]!='\n'))
{
Temp_index--;
}
if(Temp_index>0 )
{
Temp_index++;
}
// 等待数据或超时
// 这里需要实现一个等待机制,例如使用延时或轮询状态标志
// 从缓冲区读取数据到目标字符串
while (sci_c_rx_index > 0 && (sci_c_rx_buffer[sci_c_rx_index-2]!='\r') && (sci_c_rx_buffer[sci_c_rx_index-1]!='\n'))
{
//dest[len++] = sci_c_rx_buffer[--sci_c_rx_index];
dest[sci_c_rx_index - Temp_index] = sci_c_rx_buffer[--sci_c_rx_index];
len++;
}
if(sci_c_rx_index > 0 && (sci_c_rx_buffer[sci_c_rx_index-2] == '\r') && (sci_c_rx_buffer[sci_c_rx_index-1] == '\n'))
{
sci_c_rx_index -=2;
}
// 清除溢出标志(如果需要)
if (sci_c_rx_overflow)
{
sci_c_rx_overflow = 0;
// ... 处理溢出情况 ...
}
return len; // 返回接收到的字符串长度
}
/**
* 函数名:void GPIO_Init()
*@brief GPIO初始化
*设置GPIO上电默认状态
*/
void GPIO_Init()
{
EALLOW;
//用于蜂鸣器驱动
GpioCtrlRegs.GPBMUX2.bit.GPIO53 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO53 = 1;
//用于LED灯驱动
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
EDIS;
//设置默认上电GPIO口的状态
GpioDataRegs.GPADAT.bit.GPIO0 =1; //LED
GpioDataRegs.GPBDAT.bit.GPIO53 =1; //buzzer
}
void scic_init()
{
// Note: Clocks were turned on to the SCIC peripheral
// in the InitSysCtrl() function
ScicRegs.SCIFFTX.all = 0x8000;
// Reset FIFO's
//ScicRegs.SCIFFTX.all=0xE040;
//ScicRegs.SCIFFTX.all=0x8000;
ScicRegs.SCICCR.all =0x0007; // 1 stop bit, No loopback
// No parity,8 char bits,
// async mode, idle-line protocol
ScicRegs.SCICTL1.all =0x0003; // enable TX, RX, internal SCICLK,
// Disable RX ERR, SLEEP, TXWAKE
ScicRegs.SCICTL2.all =0x0003;
//ScicRegs.SCICTL2.bit.TXINTENA = 1; // 使能TX中断
ScicRegs.SCICTL2.bit.RXBKINTENA =1;
ScicRegs.SCICTL1.all =0x0023; // Relinquish SCI from Reset
}
__interrupt void cpu_timer0_isr(void)
{
CpuTimer0.InterruptCount++;
Time_1ms_flag=1;
if(++Time_10ms_Count >=10)
{
Time_10ms_Count=0;
Time_10ms_flag=1;
if(++Time_100ms_Count >=10)
{
Time_100ms_Count=0;
Time_100ms_flag=1;
if(++Time_1000ms_Count >=10)
{
Time_1000ms_Count=0;
Time_1000ms_flag=1;
}
}
}
Buzzer_Drive();
Key_Drive();
// Acknowledge this interrupt to receive more interrupts from group 1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
__interrupt void cpu_timer1_isr(void)
{
CpuTimer1.InterruptCount++;
// The CPU acknowledges the interrupt.
EDIS;
}
__interrupt void cpu_timer2_isr(void)
{ EALLOW;
CpuTimer2.InterruptCount++;
// The CPU acknowledges the interrupt.
EDIS;
}
/**
* 函数名:void LED_BUZZ_GPIO_Init()
*@brief GPIO初始化
*设置GPIO上电默认状态
*/
void LED_BUZZ_GPIO_Init()
{
EALLOW;
//用于蜂鸣器驱动
GpioCtrlRegs.GPBMUX2.bit.GPIO53 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO53 = 1;
//用于LED灯驱动
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
EDIS;
//设置默认上电GPIO口的状态
GpioDataRegs.GPADAT.bit.GPIO0 =1; //LED
GpioDataRegs.GPBDAT.bit.GPIO53 =1; //buzzer
}
//
//=========================================================================
// No more.
//=========================================================================
User_Key.c 文件:
/*
* *
* Created on: 2024年5月25日
* Author: 19269
*/
#include "Key.h" // DSP2833x Headerfile Include File
volatile unsigned char vKey;
volatile unsigned char nKey;
unsigned char g_WeighSetDelayTime = 0;
STR_SYSTEM Str_Sys;
void Key_Gpio_Init(void)
{
EALLOW;
//K1(SW3)
GpioCtrlRegs.GPAPUD.bit.GPIO13 = 0;
GpioCtrlRegs.GPAMUX1.bit.GPIO13 = 0; //使用普通IO口
GpioCtrlRegs.GPADIR.bit.GPIO13 = 0; //IO口为输入配置
GpioCtrlRegs.GPBPUD.bit.GPIO32 = 0;
GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0; //使用普通IO口
GpioCtrlRegs.GPBDIR.bit.GPIO32 = 0; //IO口为输入配置
GpioCtrlRegs.GPBPUD.bit.GPIO33 = 0;
GpioCtrlRegs.GPBMUX1.bit.GPIO33 = 0; //使用普通IO口
GpioCtrlRegs.GPBDIR.bit.GPIO33 = 0; //IO口为输入配置
EDIS;
}
void Key_Drive(void)
{
nKey = 0;
if(!PK1)nKey|=0x01;
if(!PK2)nKey|=0x02;
if(!PK3)nKey|=0x04;
}
//10ms
void KeyScan(void)
{
static unsigned char nSta = 0;
static unsigned char kCount = 0, DownTime = 0;
static unsigned int nKeyBack = 0;
if(nKey)
{
if(nKey != nKeyBack)
{
nKeyBack = nKey;
kCount = 0;
DownTime = 0;
}
else
{
if(kCount < 1)kCount++;
else
{
if(!nSta)
{
if(nKey & 0x01)vKey=KEY_01_DOWN;
else if(nKey & 0x02)vKey=KEY_02_DOWN;
else if(nKey & 0x04)vKey=KEY_03_DOWN;
else if(nKey & 0x08)vKey=KEY_04_DOWN;
else if(nKey & 0x10)vKey=KEY_05_DOWN;
else if(nKey & 0x20)vKey=KEY_06_DOWN;
// if((nKey & 0x03)==0x03)vKey=KEY_09_DOWN;
//if((nKey & 0x05)==0x05)vKey=KEY_10_DOWN;
nSta = 1;
}
else if(++DownTime > 99)
{
DownTime = 80;
if(nKey & 0x01)vKey=KEY_01_LONG;
else if(nKey & 0x02)vKey=KEY_02_LONG;
else if(nKey & 0x04)vKey=KEY_03_LONG;
else if(nKey & 0x08)vKey=KEY_04_LONG;
else if(nKey & 0x10)vKey=KEY_05_LONG;
else if(nKey & 0x20)vKey=KEY_06_LONG;
if((nKey & 0x07)==0x06){vKey=KEY_Factory;DownTime = 0;}//456
else if((nKey & 0x0a)==0x0a){vKey=KEY_Factory1;DownTime = 0;}//2+4
else if((nKey & 0x0c)==0x0c){vKey=KEY_Debug;DownTime = 0;}//3+4
// if((nKey & 0x0a00)==0x0a00){vKey=KEY_Debug;DownTime = 0;}
}
}
}
}
else if(kCount)
{
kCount--;
}
else
{
nKeyBack = 0;
DownTime = 0;
nSta = 0;
}
}
//10ms
void KeyPro(void)
{
static unsigned char Temp_Flag=0;
if(g_WeighSetDelayTime)g_WeighSetDelayTime--;
if(vKey)
{
if(Str_Sys.FactoryCheckKeyTime)//开机30秒内有效
{
if(vKey==KEY_Factory)
{
Buzzer_buzz_time_ms(1000);
Str_Sys.b_Sys_PreFactory=1;
Str_Sys.FactoryDelKeyTime=10;
vKey=0;
return;
}
}
if(Str_Sys.b_Sys_PreFactory)
{
if(vKey == KEY_01_DOWN)
{
Buzzer_buzz_time_ms(500);
}
else if(vKey == KEY_02_DOWN)
{
Buzzer_buzz_time_ms(500);
}
else if(vKey == KEY_03_DOWN)
{
Buzzer_buzz_time_ms(500);
Str_Sys.State = Sys_Select;
Str_Sys.b_Sys_PreFactory=0; //关闭工厂模式
}
vKey = 0;
return;
}
if(Str_Sys.State == Sys_Factory)
{
}
if(Str_Sys.State == Sys_Standby)//待机
{
if(vKey == KEY_03_DOWN)
{
Buzzer_buzz_time_ms(500);
Str_Sys.State = Sys_Select; //准备开始工作
Str_Sys.b_DC_Motor_Direction_Init=0; //初始化PWM
LCD12864_Task=LCD_Task2; //打开显示屏
Temp_Flag=1;
}
vKey = 0;
return;
}
else if(Str_Sys.State == Sys_Select)//功能选择状态
{
if(vKey == KEY_01_DOWN)
{
Buzzer_buzz_time_ms(300);
if(Temp_Flag<4)
{
Temp_Flag++;
}
}
else if(vKey == KEY_02_DOWN)
{
Buzzer_buzz_time_ms(300);
if(Temp_Flag>1)
{
Temp_Flag--;
}
}
else if(vKey == KEY_03_DOWN)
{
Buzzer_buzz_time_ms(300);
Str_Sys.State = Sys_PreWork; //准备开始工作
DC_Motor_running_time=0;
LCD12864_Task=LCD_Task7;
vKey = 0;
return;
}
vKey = 0;
if(Temp_Flag == 1)
{
LCD12864_Task=LCD_Task5;
}
else if(Temp_Flag == 2)
{
LCD12864_Task=LCD_Task6;
}
else if(Temp_Flag == 3)
{
LCD12864_Task=LCD_Task4;
}
else if(Temp_Flag == 4)
{
LCD12864_Task=LCD_Task0;
}
return;
}
else if(Str_Sys.State == Sys_Work || Str_Sys.State == Sys_PreWork)
{
if(vKey == KEY_03_LONG)
{
Str_Sys.State = Sys_Standby;
DC_Motor_Stop();
Buzzer_buzz_time_ms(1500);
LCD12864_Task=LCD_Task3; //关闭显示屏
Temp_Flag=0;
DC_Motor_running_time=0;
}
else if(vKey == KEY_01_DOWN)
{
Buzzer_buzz_time_ms(300);
Manage_Up();
LCD12864_Task=LCD_Task7;
}
else if(vKey == KEY_02_DOWN)
{
Buzzer_buzz_time_ms(300);
Manage_Down();
LCD12864_Task=LCD_Task7;
}
else if(vKey == KEY_03_DOWN)
{
Buzzer_buzz_time_ms(300);
DC_Motor_Direction();
LCD12864_Task=LCD_Task7;
}
Motor_Drive();
}
vKey = 0;
}
}
由于程序颇多,下面串口通信协议的程序,放一部分(串口通信协议部分)User_Serial_Port_Communication.c文件的程序:
void UART_SendBytes(uint8_t *buffer, Size_t length)
{
Size_t i = 0;
for ( i = 0; i < length; i++)
{
scic_xmit(buffer[i]);
}
}
uint8_t calculateChecksum(const uint8_t *data, uint8_t length)
{
uint8_t checksum = 0;
uint8_t i = 0;
for (i = 0; i < length; i++)
{
checksum += data[i];
}
return checksum;
}
void sendDataWithChecksum(uint8_t cmdType,uint16_t dcmotorRunTime, uint16_t pulseWidth, uint8_t direction)
{
uint8_t data[10]; // 起始字节、指令类型、运行时间(2字节)、脉冲宽度(2字节)、方向、校验和、结束字节
data[0] = 0XAA; // 起始字节
data[1] = cmdType; // 指令类型
data[2] = (dcmotorRunTime>>8) & 0xFF;
data[3] = dcmotorRunTime & 0xFF;
data[4] = (pulseWidth >> 8) & 0xFF; // 脉冲宽度高8位
data[5] = pulseWidth & 0xFF; // 脉冲宽度低8位
data[6] = direction; // 方向
// 计算校验和(不包括起始字节和结束字节)
data[7] = calculateChecksum(data, 6);
//结束字节
data[8] = 0XBB;
// 发送数据(假设有UART_SendBytes函数)
UART_SendBytes(&data[0], 9); // 发送指令类型、脉冲宽度、方向和校验和
// 在这里添加发送起始字节0xAA和结束字节0xBB的逻辑(如果需要)
}
static uint8_t rxIndex = 0; // 接收索引
uint8_t rxIndexTimeoutClear=0;
//10ms
void rxIndex_Timeout_Clear(void)
{
if(rxIndexTimeoutClear>0) //超过100ms未接收新的数据,就开始清除接收索引
{
rxIndexTimeoutClear--;
if(!rxIndexTimeoutClear)rxIndex = 0 ;
}
}
//放在SCI接收中断函数
void UART_ReceiveISR(void)
{
static uint8_t rxBuffer[MAX_BUFFER_SIZE]; // 接收缓冲区
// static uint8_t rxIndex = 0; // 接收索引
// static uint8_t expectedChecksum = 0; // 预期的校验和
uint8_t receivedChecksum;
static uint16_t Direction_Temp=0;
uint16_t pulseWidth;
uint8_t direction;
uint16_t Buzze_Running_time;
uint8_t byte = ScicRegs.SCIRXBUF.all; // 假设这个函数从串口读取一个字节
rxIndexTimeoutClear=10;
// 将字节添加到接收缓冲区
if (rxIndex < MAX_BUFFER_SIZE) {
rxBuffer[rxIndex++] = byte;
}
// 如果接收到结束字节,开始处理指令
if (byte == 0xBB && rxBuffer[0] == 0xAA && rxIndex > 3)
{
// 提取指令类型和长度(假设长度是固定的)
uint8_t cmdType = rxBuffer[1];
uint8_t dataLength = (cmdType == 0x01) ? 6 : 1; // 根据指令类型确定数据长度
// 计算接收到的数据的校验和(不包括起始字节和结束字节)
receivedChecksum = (0x00FF)&calculateChecksum(&rxBuffer[1], dataLength);
// 验证校验和
if (receivedChecksum == rxBuffer[dataLength + 1])
{
// 校验和正确,处理指令...
if (cmdType == 0x01)
{
// 解析脉冲宽度和方向...
pulseWidth = (rxBuffer[2] << 8) | rxBuffer[3];
direction = rxBuffer[4];
Buzze_Running_time = (rxBuffer[5] << 8) | rxBuffer[6];
Buzzer_buzz_time_ms(Buzze_Running_time);
//设置电机的代码
if(Direction_Temp!= direction)
{
Direction_Temp = direction;
if(direction) //正转
{
Direction=0;
}
else
{
Direction=1;
}
DC_Motor_Direction();
}
temp=pulseWidth;
if(temp==0)
{
LCD12864_Task=LCD_Task7; //更新电机运行时间
}
Motor_Drive();
if(Str_Sys.State != Sys_Work )
{
Str_Sys.State = Sys_Work;
}
}
else if (cmdType == 0x02)
{
// 发送状态响应...
sendDataWithChecksum(0X82,DC_Motor_running_time,temp,Direction);
}
else if (cmdType == 0x03)
{
LCD12864_Task=LCD_Task8;
}
}
else
{
// 校验和错误处理
}
// 重置接收索引和预期的校验和
rxIndex = 0;
// expectedChecksum = 0;
}
}
六、实验结论与体会
1、实验结论
通过实验,我们成功实现了通过串口和按键控制直流电机的运行,并通过液晶屏实时显示了电机的运行状态。实验结果表明,系统具有较高的稳定性和可靠性,能够满足实际应用的需求。同时,我们也发现了一些可以改进的地方,如提高串口通信的速率、优化按键扫描算法等。
2、体会
实验过程加深了对电机控制、DSP单片机编程、串口通信和人机界面设计等理论知识的理解。
当项目成功运转,电机按照预期运行,液晶屏正确显示状态时,有一种很大的成就感。
在解决DSP TMS320F28335使用snprintf()函数编译时内存不足的问题时,查阅很多相关资料,获知解决方法是通过CMD文件将程序存在TMS320F28335的闪存中,snprintf()函数使用时内存不足的问题得到解决。
内容说明
1.上传完整项目源代码:Button_Serial_Control_Motor_Lcd12864
链接:https://pan.baidu.com/s/1PelikmdGEoWOyANvtl1yow?pwd=1926
提取码:1926
2.串口通信的发送与接收都是使用中断方式
3.通信协议
(1)数据校验使用累加和校验
(2)数据长度(dataLength)确定不够智能,C语言基础好的学者一眼就能看出并知道如何修改;当初是为给朋友看的,所以才写了很简单 ,也是让他们对数据有清晰的认知
(3)数据响应发送做得不很好,此项目响应返回的数据不是很大,因此影响不是很大;正确做法是设置发送标志,将发送内容或数据在主循环中执行并且发送完后立即清除发送标志
(4)这个项目内容丰富,应该能帮助一些学者将一些需要的功能模块融合在一些,例如串口控制LED灯、串口控制蜂鸣器等待,只需要约定好数据的命令类型控制起来也很方便
(5)此项目的通信协议很简单,希望对初学者有帮助
参考资料
1、三兄弟嵌入式 ,网址: http://sxdembed.taobao.com
2、硬件:玻尔电子宙斯盾板之SXD28335B_QFP
注意:其它缺失文件请参考三兄弟嵌入式的玻尔电子宙斯盾板之SXD28335B_QFP软件例程,本次只是对主要功能的要求进行验证。