为电赛准备学习的MSP430笔记
提示:主要跟着这个大佬学的
时钟配置和闪烁的LED
时钟系统结构
(1)5个时钟来源
时钟系统模块具有5个时钟来源。
① XT1CLK:低频/高频振荡器,可以使用32768Hz的手表晶振、标准晶体、谐振器或4~32MHz的外部时钟源;
② VLOCLK:内部超低功耗低频振荡器,典型频率12kHz;
③ REFOCLK:内部调整低频参考振荡器,典型值为32768Hz;
④ DCOCLK:内部数字时钟振荡器,可由FLL稳定后得到;
⑤ XT2CLK:高频振荡器,可以是标准晶振、谐振器或4~32MHz的外部时钟源。
(2)3个时钟信号
时钟系统模块可以产生3个时钟信号供CPU和外设使用。
① ACLK:辅助时钟(Auxiliary Clock)。可以通过软件选择XT1CLK、REFOCLK、VLOCLK、DCOCLK、DCOCLKDIV或XT2CLK(当XT2CLK可用时)。DCOCLKDIV是FLL模块内DCOCLK经过1/2/4/8/16/32分频后获得的。ACLK主要用于低速外设。ACLK可以再进行1/2/4/8/16/32分频,ACLK/n 就是ACLK 经过1/2/4/8/16/32分频后得到的,也可以通过外部引脚进行输出。
② MCLK:主时钟(Master Clock)。MCLK的时钟来源与ACLK相同,MCLK专门供CPU使用,MCLK配置得越高,CPU的执行速度就越快,功耗就越高。一旦关闭MCLK,CPU也将停止工作,因此在超低功耗系统中可以通过间歇启用MCLK的方法降低系统功耗。MCLK也可经1/2/4/8/16/32分频后供CPU使用。
③ SMCLK:子系统时钟(Subsystem Master Clock)。SMCLK的时钟来源与ACLK相同,SMCLK主要用于高速外设,SMCLK也可以再进行1/2/4/8/16/32分频。
MSP430具有高达25Mhz的主频,让我们来配置它
//将主时钟MCLK,子系统时钟SMCLK设为25Mhz,MCLK主要用于CPU,SMCLK主要用于高速外设
//将辅助时钟ACLK设为32768Hz,主要用于低速外设
void SystemClock_Init(void)
{
PMM_setVCore(PMM_CORE_LEVEL_3); //高主频工作需要较高的核心电压
//XT1振荡器的引脚复用 P5.4 XIN输入 P5.5 XOUT输出 选择单片机内部的晶振
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN4);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN5);
//起振XT1 若在LF工作下的XT1振荡器为32768hz的低频晶振,与之相匹配为12pf的电容 XCAP(0: 2pf ,1: 5.5pf, 2: 8,5pf, 3:12pf )
UCS_turnOnLFXT1(UCS_XT1_DRIVE_3,UCS_XCAP_3);
//XT2引脚复用 P5.2 XIN输入 P5.5 XOUT输出 板载XT2晶振为CSTCR4M00G15L99,4MHz
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN2);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5, GPIO_PIN3);
//起振XT2
UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);
//XT2作为FLL(锁频环)的时钟源,输出为DCOCLK(内部数字时钟振荡器),8分频 先8分频,再50倍频 4MHz / 8 * 50 = 25MHz
UCS_initClockSignal(UCS_FLLREF, UCS_XT2CLK_SELECT, UCS_CLOCK_DIVIDER_8);
//用于设置锁相环倍频系数,第一个系数为目标主时钟的频率 单位kHz,第二个系数为= 第一个系数/FLL的频率 (既刚刚得到的4Mhz/8) 50 = 25000khz/(0.5M) 1000k= 1M 1M =10^6khz
UCS_initFLLSettle(25000, 50);
//XT1作为ACLK时钟源 = 32768Hz 1分频= /1
UCS_initClockSignal(UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为MCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_MCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//DCOCLK作为SMCLK时钟源 = 25MHz
UCS_initClockSignal(UCS_SMCLK, UCS_DCOCLK_SELECT, UCS_CLOCK_DIVIDER_1);
//设置外部时钟源的频率,使得在调用UCS_getMCLK, UCS_getSMCLK 或 UCS_getACLK时可得到正确值 (XT1CLK_frequency,XT2CLK_frequency)
UCS_setExternalClockSource(32768, 4000000);
}
GPIO
电机编码器的线数为11(一圈方波数)30(减速比)=330
线数太低了,我们使用4倍频来使它加倍,3304=1320
我们需要在AB相的上升下降沿都计数(相当于原来的4倍)
现在我们用外部中断来实现编码器计数
void Counter_Left_Init (void)
{
//配置时,先配置功能选择寄存器PxSEL,
//若为I/O端口功能,则继续配置方向寄存器PxDIR;
//若为输入,则继续配置中断使能寄存器PxIE;
//若允许中断,则继续配置中断触发沿选择寄存器PxIES。
//只有P1 和P2 有中断功能
//P1.3 P1.6 上拉输入并使能interrupt enabled 使能寄存器需和输出寄存器配合使用,才能完成上拉/下拉电阻的配置。
GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P1,GPIO_PIN3 +GPIO_PIN6);
GPIO_enableInterrupt(GPIO_PORT_P1,GPIO_PIN3 +GPIO_PIN6);
//设置中断类型 Hi/Lo edge
GPIO_selectInterruptEdge(GPIO_PORT_P1,GPIO_PIN3,GPIO_HIGH_TO_LOW_TRANSITION|GPIO_LOW_TO_HIGH_TRANSITION);
GPIO_selectInterruptEdge(GPIO_PORT_P1,GPIO_PIN6,GPIO_HIGH_TO_LOW_TRANSITION|GPIO_LOW_TO_HIGH_TRANSITION);
//清除中断标志位 IFG cleared
GPIO_clearInterrupt(GPIO_PORT_P1,GPIO_PIN3 +GPIO_PIN6);
//启用可屏蔽中断(具有中断功能的片上外设产生)
_BIS_SR(GIE);
}
#pragma vector=PORT1_VECTOR // P1口中断源
__interrupt
void Port_1 (void)
{
// if(GPIO_getInterruptStatus (GPIO_PORT_P1, GPIO_PIN3)||GPIO_getInterruptStatus (GPIO_PORT_P1, GPIO_PIN6))
// {
// Counter_Left++;
// GPIO_clearInterrupt(GPIO_PORT_P1,GPIO_PIN3 +GPIO_PIN6);
// }
_EINT();//恢复总的中断允许,避免中断处理延迟
if(GPIO_getInterruptStatus(GPIO_PORT_P1, GPIO_PIN3))//当发生中断
{
Counter_Left++;
GPIO_clearInterrupt(GPIO_PORT_P1,GPIO_PIN3);
}
if(GPIO_getInterruptStatus(GPIO_PORT_P1, GPIO_PIN6))
{
Counter_Left++;
GPIO_clearInterrupt(GPIO_PORT_P1,GPIO_PIN6);
}
}
定时器A
我们想实现每秒sec加1
设得中断频率为25MHz / 10 / 25000 = 100Hz //即每秒刷新100下,0.01s定时
一共两个中断模式CCR0中断 TAIE中断,一个使能一个不使能
一般使用增计数模式CCR0中断
定时器A增计数模式CCR0中断
//定时0.01s
void TimerA1Init()
{
Timer_A_initUpModeParam param = {0};
param.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //时钟源选为SMCLK = 25MHz
param.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_10; //10分频
param.timerPeriod = 25000-1; //计数值设为25000 - 1
param.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE; //不使能 TAIE中断
param.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_ENABLE; //使能CCR0中断
param.timerClear = TIMER_A_DO_CLEAR; //把定时器的定时计数器,分频计数器的计数值清零
param.startTimer = true; //初始化后立即启动定时器
//设得中断频率为25MHz / 10 / 25000 = 100Hz
Timer_A_initUpMode(TIMER_A1_BASE,¶m); //配置定时器A为增计数模式
}
例如我们配置定时器1的0寄存器(在32里应该叫通道)
TIMER_A1_BASE
TIMER1_A0_VECTOR //向量名和中断函数名必须一致
TIMER1_A0_ISR //前面的1 是A1的1
#pragma vector=TIMER1_A0_VECTOR
__interrupt
void TIMER1_A0_ISR (void)
{
if(n++==100)
{
second++;
n=0;
}
}
定时器A
Timer_A_outputPWM(uint16_t baseAddress, Timer_A_outputPWMParam ∗param)
//计时器A运行在增计数模式产生PWM
为什么说这句话是在增计数模式下呢,我们并没看到配置定时器在增计数模式,所以我们看看这个库函数怎么写的。
它在第一句把所有位进行了一个清零的操作,然后在第4句将TIMER_A_UP_MODE置1,至此我们知道它将增模式也一起配置了。
已知增计数模式下是将0加到TAxCCR0再重新计数,我们看到它将TAxCCR0置为我们可以设置的时间周期timerPeriod。dutyCycle是什么呢,下面会解释。
然后我们在这里将compareOutputMode设置 复位置位 模式
param.compareOutputMode = TIMER_A_OUTPUTMODE_RESET_SET;
增计数模式下,定时器比较输出
在这个模式下,我们发现在计数到TAxCCR1时为高电平输出,TAxCCR1~TAxCCR0时为低电平输出,而TAxCCR1就是我们上文未解释的dutyCycle(占空比),当我们将TAxCCR0设为100,TAxCCR1设为50时,那我们的占空比就为50/100 = 50%。
我们想实现电机初始化
P1.2 对应 TA0.1
void Motor_Left_Init (void)
{
//P1.2 as PWM output
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN2);//P1.2复用输出
//GPIO_setAsOutputPin (GPIO_PORT_P4,GPIO_PIN0 +GPIO_PIN3 ); //GPIO输出
//GPIO_setOutputHighOnPin (GPIO_PORT_P4, GPIO_PIN0); //高电位输出
//GPIO_setOutputLowOnPin (GPIO_PORT_P4, GPIO_PIN3); //低电位输出
//Generate PWM - Timer runs in Up mode
Timer_A_outputPWMParam param = {0};
param.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; // 时钟源选为SMCLK = 25MHz
param.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1; //不分频
//装载值设为1000 - 1
param.timerPeriod =1000-1; //时钟周期
//频率为25M/1/1000 = 25000hz
//P1.2 对应 TA0.1 故设为TIMER_A_CAPTURECOMPARE_REGISTER_1
param.compareRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1; //选择比较寄存器
param.compareOutputMode = TIMER_A_OUTPUTMODE_RESET_SET; 为
//pwm最大1000
param.dutyCycle = 0;//这个值compare = 300 就相当于300/1000 30%的占空比
Timer_A_outputPWM(TIMER_A0_BASE, ¶m);
}
Timer_A_setCompareValue(uint16_t baseAddress, uint16_t compareRegister, uint16_t
compareValue)
//设置捕获比较寄存器的值
void Motor_SetSpeed_Left(int8_t Speed)
{
GPIO_setAsOutputPin (GPIO_PORT_P4,GPIO_PIN0 +GPIO_PIN3 );//初始化GPIO输出
if (Speed >= 0) //正数 代表正转
{
GPIO_setOutputHighOnPin (GPIO_PORT_P4, GPIO_PIN0); //高电位输出
GPIO_setOutputLowOnPin (GPIO_PORT_P4, GPIO_PIN3); //低电位输出
Timer_A_setCompareValue(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, Speed)
}
else
{
GPIO_setOutputLowOnPin (GPIO_PORT_P4, GPIO_PIN0); //高电位输出
GPIO_setOutputHighOnPin (GPIO_PORT_P4, GPIO_PIN3); //低电位输出
Timer_A_setCompareValue(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, -Speed)//因为SetCompare要传的是无符号整型数 所以 speed加负号
}
}
最终输出速度,小车开始转
void Front_Run(void)
{
Motor_SetSpeed_Left(300);
Motor_SetSpeed_Right(300);
}
GPIO_setAsOutputPin(uint8_t selectedPort, uint16_t selectedPins);
GPIO_setAsInputPin(uint8_t selectedPort, uint16_t selectedPins);
GPIO_setAsInputPinWithPullDownResistor(uint8_t selectedPort, uint16_t selectedPins);
GPIO_setAsInputPinWithPullUpResistor(uint8_t selectedPort, uint16_t selectedPins);
GPIO_setDriveStrength(uint8_t selectedPort, uint16_t selectedPins, uint8_t
driveStrength);
GPIO_setAsPeripheralModuleFunctionOutputPin(uint8_t selectedPort, uint16_t
selectedPins);
GPIO_setAsPeripheralModuleFunctionInputPin(uint8_t selectedPort, uint16_t
selectedPins);
GPlO引脚可以被配置为在外设模块中操作,通过使用配置GPIO分配功能
GPIO_setOutputHighOnPin(uint8_t selectedPort, uint16_t selectedPins);
GPIO_setOutputLowOnPin(uint8_t selectedPort, uint16_t selectedPins);
GPIO_toggleOutputOnPin(uint8_t selectedPort, uint16_t selectedPins);
GPIO_getInputPinValue(uint8_t selectedPort, uint16_t selectedPins);
一般在程序的初始化阶段对端口进行配置。
配置时,先配置功能选择寄存器PxSEL,
若为I/O端口功能,则继续配置方向寄存器PxDIR;
若为输入,则继续配置中断使能寄存器PxIE;
若允许中断,则继续配置中断触发沿选择寄存器PxIES。
简单配置电机的两个引脚 ,第一句必不可少
GPIO_setAsOutputPin (GPIO_PORT_P4,GPIO_PIN0 +GPIO_PIN3 ); //GPIO输出
GPIO_setOutputHighOnPin (GPIO_PORT_P4, GPIO_PIN0); //高电位输出
GPIO_setOutputLowOnPin (GPIO_PORT_P4, GPIO_PIN3); //低电位输出
UART通信
P3.3(TX)和P3.4(RX)为USCI_A0串口
P4.4(TX)和P4.5(RX)为USCI_A1串口
如图所示,异步通信字符格式由5个部分组成:一个起始位、7位或8位数据位、一个奇/偶/无校验位、一个地址位和一个或两个停止位。其中,用户可以通过软件设置数据位、停止位的位数,还可以设置奇偶校验位的有无。通过选择时钟源和波特率寄存器的数据来确定传输速率。UCMSB控制位用来设置传输的方向和选择最低位还是最高位先发送。一般情况下,对于UART通信选择先发送最低位。
USCI_A_UART_clearInterrupt(uint16_t baseAddress, uint8_t mask)
//清除UART中断源,使其不再断言。当使用中断向量生成器时,最高中断标志将自动清除
USCI_A_UART_enableInterrupt(uint16_t baseAddress, uint8_t mask)
//启用UART中断
huart.parity = USCI_A_UART_NO_PARITY;
//设置奇偶校验位,USCI_A_UART_ODD_PARITY奇校验 USCI_A_UART_EVEN_PARITY偶校验 USCI_A_UART_NO_PARITY 无校验位
huart.msborLsbFirst = USCI_A_UART_LSB_FIRST;
//设置最低最低位优先或最高位优先的数据发送和接收方式 通常我们接收数据中的最低位
//LSB(Least Significant Bit)是“最低有效位”。MSB(Most Significant Bit)是“最高有效位”。
huart.numberofStopBits = USCI_A_UART_ONE_STOP_BIT;
//设置停止位 USCI_A_UART_ONE_STOP_BIT 一位USCI_A_UART_TWO_STOP_BITS 两位
huart.uartMode = USCI_A_UART_MODE;
//选择模式 选取默认的异步通讯模式 当三个或多个设备通信时,
USCI支持空闲行 USCI_A_UART_IDLE_LINE_MULTI_PROCESSOR_MODE和
地址位多处理器通信格式 USCI_A_UART_ADDRESS_BIT_MULTI_PROCESSOR_MODE
还一个模式为自动波特率检测 USCI_A_UART_AUTOMATIC_BAUDRATE_DETECTION_MODE
第一个参数为选择串口基地址,第二个参数为波特率
bool UART_Init(uint16_t baseAddress, uint32_t Baudrate)
{
float UART_Temp = 0;
USCI_A_UART_initParam huart = {0};
//将所用引脚复用为UART模式
if(baseAddress == USCI_A0_BASE) //P3.3, P3.4 = USCI_A0 TXD/RXD
{
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P3, GPIO_PIN3);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P3, GPIO_PIN4);
}
else if(baseAddress == USCI_A1_BASE) //P4.4, P4.5 = USCI_A1 TXD/RXD
{
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P4, GPIO_PIN4);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P4, GPIO_PIN5);
}
//当目标波特率较低时,UART选用时钟源为ACLK,反之选择SMCLK
//调用UCS_getACLK/UCS_getSMCLK前需正确调用UCS_setExternalClockSource函数,我已加到SystemClock_Init函数中
if(Baudrate <= 9600)
{
huart.selectClockSource = USCI_A_UART_CLOCKSOURCE_ACLK;
UART_Temp = (float)UCS_getACLK()/Baudrate;
}
else
{
huart.selectClockSource = USCI_A_UART_CLOCKSOURCE_SMCLK;
UART_Temp = (float)UCS_getSMCLK()/Baudrate;
}
if(UART_Temp < 16) //当分频因子小于16时,采用低频波特率设置
huart.overSampling = USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION;
else //反之,采用过采样波特率设置
{
huart.overSampling = USCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION;
UART_Temp /= 16;
}
huart.clockPrescalar = (int)UART_Temp;
if(huart.overSampling == USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION)
{ //低频波特率设置 UCBRSx
huart.secondModReg = (int)((UART_Temp - huart.clockPrescalar) * 8);
}
else
{ //过采样波特率设置 UCBRFx
huart.firstModReg = (int)((UART_Temp - huart.clockPrescalar) * 16);
}
huart.parity = USCI_A_UART_NO_PARITY;
huart.msborLsbFirst = USCI_A_UART_LSB_FIRST;
huart.numberofStopBits = USCI_A_UART_ONE_STOP_BIT;
huart.uartMode = USCI_A_UART_MODE;
if (STATUS_FAIL == USCI_A_UART_init(baseAddress, &huart)) //如果成功返回一个STATUS_SUCCESS
{ //初始化对应串口
return STATUS_FAIL;
}
//Enable UART module for operation 使能对应串口
USCI_A_UART_enable(baseAddress);
//Enable Receive Interrupt 启用串口中断
USCI_A_UART_clearInterrupt(baseAddress, USCI_A_UART_RECEIVE_INTERRUPT);
USCI_A_UART_enableInterrupt(baseAddress, USCI_A_UART_RECEIVE_INTERRUPT);
return STATUS_SUCCESS;
}
串口中断函数
USCI_A_UART_transmitData(uint16_t baseAddress, uint8_t transmitData)
//通过UART模块传输一个字节
USCI_A_UART_receiveData(uint16_t baseAddress)
//接收一个已发送到UART模块的字节。
下面实现将接收到的字符回传 接收一个字符就回发一个
#pragma vector=USCI_A0_VECTOR
__interrupt void USCI_A0_ISR (void)
{
uint8_t receivedData = 0;
switch (__even_in_range(UCA0IV,4))
{
//Vector 2 - RXIFG
case 2:
receivedData = USCI_A_UART_receiveData(USCI_A0_BASE);
USCI_A_UART_transmitData(USCI_A0_BASE,receivedData);
break;
default:
break;
}
}
MSP430之__even_in_range
使用__even_in_range 的好处是可以生成效率比较高的代码,在判断多中断源的中断的来源时可以使用此函数。
原型:unsigned short __even_in_range(unsigned short value, unsignedshort upper_limit);
功能:只能与switch 语句结合使用,判断value 是否为偶数且小于等于upper_limit。
之前已经配置好了串口,
在库函数中,没有相关的函数配置发送数据位数。只在注释里找到。
从手册里发现默认是8位数据位,
所以我们msp430串口的相关配置是:8位数据位,无奇偶校验,1位停止位
UART_Init(USCI_A1_BASE, 115200);//做一个115200的初始化
同样,我们在openmv的也进行相同操作
from pyb import UART
uart = UART(3,115200) #定义串口3变量 #P4 P5
uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters
# 8位数据位 无奇偶校验 1位停止位
最后openmv的数据传给msp430
uart.write(str(output))
我们要实现在openmv的ide里知道是否已经传到msp430,所以我们打印从msp430回传的数据
def recive_data():
global uart
if uart.any():
tmp_data = uart.readline();
print(tmp_data)
recive_data();
库函数I2C驱动OLED屏幕
我们用四针脚的oled来显示,5529芯片上的资源有
P3.0 SDA //P3.1SCL
P4.1SDA //P4.2SCL
看上面的博客的需要改端口的在这可以改
也可以参考下面这个博客
OLED模块//用这个可以点亮 试过
然后在这个位置修改
先搞懂一下I2C协议,这个博主讲的非常明白
I2C协议靠这16张图彻底搞懂(超详细)
然后我从发现P3.5 P3.6在芯片手册上明明没有标注有SDA功能,却可以当作SDA,SCL来用,于是深究这个问题,这个是类似的博客发的驱动oled代码
我先找到一篇博客,TI - MCU - MSP430使用指南14 -> I2C通信(eUSCI),上面说eUSCI_B有追加IIC的功能,手册上也雀实有,不过还是没有讲能用的资源有多少,
于是我开始回到GPIO的引脚上思索这个问题,SDA是双向时钟线
也就意味着双方的两个引脚必须都要有写和读的功能,而MSP430所有的引脚都有写和读的功能,所以我觉得所有的引脚都可以配置成IIC的SDA、SCL,不过这只是我的一个猜测,具体要是有不对的地方,望指正。
然后P1~P2还有中断的功能。
以上就是今天要讲的内容,本文仅仅简单介绍了MSP430的使用,至此小车是可以跑起来了,为电赛强制用TI的板子做了一个简单的备战。
最后感谢一下女朋友望月12138写的代码。
以下是找的一些MSP430F5529的资料
链接:https://pan.baidu.com/s/1nGVduyZzR0xHqeAV6XStqA?pwd=dwuv
提取码:dwuv