1.延时函数
- 空代码延时 执行一条指令的时间是:1/32M
js
//代码延时 执行一条指令的时间是:1/32M
void delay_S(uint32_t s)
{
for(int i=0;i<s;i++)
{
for(long j=0;j<32000000;j++)
{
__asm__("nop"); //空指令
}
}
}
- delay_cycles
js
假设主频为 32MHz,想延时 1 微秒:(主频已经设置为80MHz)
- 1 微秒 = 1e-6 秒
- 32MHz = 32,000,000 Hz
- 1 微秒内有 32 个时钟周期、
delay_cycles(32); // 大约延时 1 微秒
- 滴答定时器延时阻塞版
js
滴答定时器延时阻塞版
void delay_ms(uint32_t ms)
{
uint32_t ticks=ms*(CPUCLK_FREQ/1000);
uint32_t count_new=0,count_old=0;
uint32_t count;
count_old= SysTick->VAL;
while (1)
{
count_new=SysTick->VAL;
if(count_old!=count_new)
{
if(count_new<count_old)
{
count = count+(count_old-count_new) ;
}
else if(count_new>count_old)
{
count = count+ SysTick->LOAD- count_new+count_old;
}
count_old=count_new;
if(count>=ticks) return;
}
}
}
- 滴答定时器中断延时
js
volatile uint32_t delay_vlaue;
void delay_ms(uint32_t ms)
{
delay_vlaue=ms;
while(delay_vlaue!=0);
}
void SysTick_Handler(void)
{
delay_vlaue--;
}
2.按键
js
int main(void)
{
SYSCFG_DL_init();
while (1)
{
//1秒周期
delay_ms(200);
if(DL_GPIO_readPins(key_PORT,key_PIN_A18_PIN)!=0)
{
delay_ms(30);
if(DL_GPIO_readPins(key_PORT,key_PIN_A18_PIN)!=0)
{
DL_GPIO_togglePins(LED_PORT, LED_PIN_A14_PIN);
}
}
}
}
3.外部中断

- 对于按键外部中断的配置(PA18,通道0)
事件管理器传输的事件包括:
- 作为中断请求 (IRQ) 传输到 CPU 的外设事件(静态事件) -- 示例:GPIO 中断会发送到 CPU
- 作为 DMA 触发器传输到 DMA 的外设事件(DMA 事件) -- 示例:传输到 DMA、请求 DMA 传输的 UART 数据接收触发器
- 传输到另一个外设以直接触发硬件中操作的外设事件(通用事件) -- 示例:TIMx 计时器外设将周期性事件发布到 ADC 订阅者端口,ADC 使用该事件触发采样开始(ADC触发采样)
- 配置静态事件时,通用事件配置为通道0
静态事件(类似于32的外部中断)

- 只需要对按键配置中断,不用开启通道
js
#include "ti_msp_dl_config.h"
//滴答定时器中断延时
volatile uint32_t delay_vlaue;
void delay_ms(uint32_t ms)
{
delay_vlaue=ms;
while(delay_vlaue!=0);
}
void SysTick_Handler(void)
{
delay_vlaue--;
}
uint16_t test_data1=0;
//GPIO外部中断函数,静态事件
void GROUP1_IRQHandler(void)
{
// 判断是否为GPIOA引脚产生的中断
if(DL_Interrupt_getStatusGroup(DL_INTERRUPT_GROUP_1,DL_INTERRUPT_GROUP1_GPIOA))
{
if(DL_GPIO_readPins(key_PORT,key_PIN_A18_PIN)>0)
{
test_data1++;
// 切换LED状态
DL_GPIO_togglePins(LED_PORT, LED_PIN_A14_PIN);
}
// 清除GPIOA引脚的中断挂起标志,避免重复触发
DL_Interrupt_clearGroup(DL_INTERRUPT_GROUP_1,DL_INTERRUPT_GROUP1_GPIOA);
}
}
int main(void)
{
SYSCFG_DL_init();
NVIC_EnableIRQ(GPIOA_INT_IRQn);//开启按键引脚的GPIOA端口中断
while (1)
{
}
}
通用事件(new)

- 按键配置为通道2

-
将lED配置为对应的通道,设置为触发后翻转
-
配置为通用事件外部中断后就不用在初始化时开启中断使能了
NVIC_EnableIRQ(GPIOA_INT_IRQn);
4. 串口

- 串口0,1,2都在PD0总线上 ,可以看到在时钟是ULPCLK
- 串口3在PD1总线上

- 硬件流控制主要通过这个RTS和CTS控制,为了同步时钟,一般不开启
发送两种
- 串口发送字符串两种实现方式'
- 第一种正常
DL_UART_transmitData
js
//串口发送单个字符
void uart0_send_char(char ch)
{
//当串口0忙的时候等待,不忙的时候再发送传进来的字符
while( DL_UART_isBusy(UART_0_INST) == true );
//发送单个字符
DL_UART_transmitData(UART_0_INST, ch);
}
//串口发送字符串
void uart0_send_string(char* str)
{
//当前字符串地址不在结尾 并且 字符串首地址不为空
while(*str!=0&&str!=0)
//发送字符串首地址中的字符,并且在发送完成之后首地址自增
uart0_send_char(*str++);
}
- 第二种
DL_UART_transmitDataBlocking
能自动检测发送寄存器是否已被填充(推荐
js
void uart0_send_string(char* str)
{
//当前字符串地址不在结尾 并且 字符串首地址不为空
while(*str!=0&&str!=0)
// while(*str!='\0')
//发送字符串首地址中的字符,并且在发送完成之后首地址自增
DL_UART_transmitDataBlocking(UART_0_INST,*str++);
}
char str[50];
sprintf(str,"res_sin:%.2f\r\n", res);
uart0_send_string( str);
delay_cycles(CPUCLK_FREQ);
- 串口重定向函数(都要写
js
//串口重定向
int fputc(int ch, FILE *stream)
{
DL_UART_transmitDataBlocking(UART_0_INST,ch);
return ch;
}
int fputs(const char *ptr, FILE *stream)
{
uint16_t i,len;
len=strlen(ptr);
for( i=0;i<len;i++)
{
DL_UART_transmitDataBlocking(UART_0_INST,ptr[i]);
}
return len;
}
int puts(const char *ptr)
{
int count=fputs(ptr, stdout);
count+=fputs("\n",stdout);
return count;
}
接收
- 开中断
- 需要注意串口清除中断标志位函数不用在中断里
!!!NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);这句一定不能加!!!
js
//在初始化里加上
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
//串口的中断服务函数
char rx_buffer[100];
uint8_t buffer_index;
uint8_t buffer_full;
void UART_0_INST_IRQHandler(void)
{
//如果产生了串口中断
if(DL_UART_getPendingInterrupt(UART_0_INST)==DL_UART_IIDX_RX)
{
int rx_data = DL_UART_receiveDataBlocking(UART_0_INST);
if(rx_data=='\r'||rx_data=='\n')
{
rx_buffer[buffer_index] = '\0';
buffer_full = 1; //串口接口函数当数据接收完成,可以用buffer_full来处理串口接收数据
buffer_index = 0;
}
else if(buffer_index< sizeof(rx_buffer) - 1)
{
rx_buffer[buffer_index++]=rx_data;
}
//将保存的数据再发送出去
DL_UART_transmitDataBlocking(UART_0_INST,rx_data);
}
}
5.定时器(8.29)

-
通用计时器 (TIMGx) 的具体特性包括:(类似通用定时器)
- 具有重复重新加载模式的 16 位递增、递减或递增/递减计数器
- 具有重复重新加载模式的 32 位递增、递减或递增/递减计数器
- 可选和可配置的时钟源
- 用于对计数器时钟频率进行分频的 8 位可编程预分频器
- 两个独立通道,用于:
- 输出比较
- 输入捕捉
- PWM 输出
- 单稳态模式
- CC 寄存器在 TIMG7 和 TIMG12 中可用
- 用于加载的影子寄存器在 TIMG7 中可用
- 霍尔传感器输入的交叉触发事件逻辑
- 支持中断/DMA 触发生成以及跨外设(例如 ADC)触发功能
-
通用计时器 (TIMAx ) 的具体特性包括:(类似高级定时器)
- 具有重复重新加载模式的 16 位递减或加减计数器
- 可选和可配置的时钟源
- 用于对计数器时钟频率进行分频的 8 位可编程预分频器
- 重复计数器,仅在计数器的给定周期数之后生成中断或事件
- 最多四个独立通道,用于:
- -- 输出比较
- -- 输入捕捉
- -- PWM 输出
- -- 单稳态模式
- 用于加载的影子寄存器和 CC 寄存器在 TIMA0 和 TIMA1 都可用
- 互补输出 PWM
- 具有可编程死区插入功能的非对称 PWM
配置

- 区别于stm32,g3507的定时器周期是自己写的,不用配置arr,只用配置PSC(arr他会自己计算)
- frq=4MHz/1/100/40000=1HZ
6.PWM

-
timA才有互补输出功能,IIMG没有互补输出功能(
结尾带N的就是互补输出,例如当TIMA0_C3输出高电平时TIMA0_C3N就输出低电平
)- 互补输出:就是同时输出两个PWM,极性相反
-
timA有4个通道输出,IIMG只有两个通道输出
-
PWM呼吸灯实验
js
/*test_pwm pwm*/
#define pwm_period 1000
static const DL_TimerG_PWMConfig gPWM_0Config = {
.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN,//边沿触发
.period = pwm_period,//周期
.isTimerWithFourCC = false,
.startTimer = DL_TIMER_START,
};
int main(void)
{
SYSCFG_DL_init();
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
//使能定时器中断 TIMA0
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
uart0_send_string("start pwm hooxi led test\r\n");
//PWM周期调整,通过结构体调节 TIMG6
DL_TimerG_initPWMMode( PWM_0_INST, (DL_TimerG_PWMConfig *) &gPWM_0Config);
while (1)
{
//delay_ms(50);
for(int i=pwm_period;i>0;i--)
{
DL_Timer_setCaptureCompareValue(PWM_0_INST,i,DL_TIMER_CC_0_INDEX);
delay_ms(1);
}
for(int j=0;j<pwm_period;j++)
{
DL_Timer_setCaptureCompareValue(PWM_0_INST,j,DL_TIMER_CC_0_INDEX);
delay_ms(1);
}
}
}
7.ADC

-
MSPM0G3507采用的是逐次逼近型的12位ADC ,它有17个多路复用通道可以转换。17个外部通道,都对应单片机的某个引脚,这个引脚不是固定的,详情请参考引脚图或者数据手册。
-
各种通道的 A/D 转换可以配置成 单次、序列转换 模式。
-
单次转换模式: 每次进行一次ADC转换后,ADC会自动停止,并将结果存储在ADC数据寄存器中。
-
重复单次转换模式: 当ADC完成一次转换后,它会自动启动另一次转换,持续的进行转换,直到外部触发或者软件触发的方式停止连续转换。
-
多通道顺序单次转换模式: 用于对多个输入通道进行依次转换。在该模式下,ADC会根据配置的通道采集顺序,对多个通道进行单次采样并转换。
-
多通道顺序重复转换模式: 用于对多个输入通道进行依次重复转换。在该模式下,ADC会根据配置的通道采集顺序,对多个通道进行重复采样并转换。
-
ADC基本参数
-
分辨率: 表示ADC转换器的输出精度,通常以位数(bit)表示,比如8位、10位、12位等,位数越高,精度越高。MSPM0L1306支持8、10、12位的分辨率。
-
采样率: 表示ADC对模拟输入信号进行采样的速率,通常以每秒采样次数(samples per second,SPS)表示,也称为转换速率,表示ADC能够进行多少次模拟到数字的转换。MSPM0G3507的SPS为4Msps(尽量不要超过)。
-
电压基准: ADC的电压基准是用于与模拟输入信号进行比较,从而实现模拟信号到数字信号的转换的一个参考电压。这个基准电压的准确性和稳定性对ADC的转换精度有着决定性的影响。而MSPM0G3507可以支持软件选择三种基准:
- (1)1.4V 和 2.5V 的可配置内部专用 ADC 基准电压 (VREF)
- (2)MCU 电源电压 (VDD) (一般是这个)
- (3)通过 VREF+和 VREF- 引脚为 ADC 提供外部基准。
- 如未配置电压基准则默认使用MCU电源电压作为ADC电压基准。
单通道单次手动触发(没实现)

js
float adc_getvalue(void)
{
uint16_t adc_value=0;
//开启adc转换
DL_ADC12_startConversion(ADC12_0_INST);
//如果adc在忙,则卡住
while (DL_ADC12_getStatus(ADC12_0_INST)!=DL_ADC12_STATUS_CONVERSION_IDLE);
//暂停adc转换
DL_ADC12_stopConversion(ADC12_0_INST) ;
//获取存储在内存0的转换结果
adc_value=DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
//开启adc转换,因为单次转换会导致采集停止
DL_ADC12_enableConversions( ADC12_0_INST);
return adc_value*3.3f/4095;
}
int main(void)
{
SYSCFG_DL_init();
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
uart0_send_string("adc test \r\n");
while (1)
{
adc_data=adc_getvalue();
sprintf(str, "adc_value :%.2f\r\n", adc_data);
//printf("adc_value :%.2f\r\n",adc_data);
uart0_send_string(str);
delay_ms(500);
}
}
多通道单次手动触发(没实现)

js
float adc_data[2]={0};
void adc_getvalue(void)
{
//开启adc转换
DL_ADC12_startConversion(ADC12_0_INST);
//如果adc在忙,则卡住
while (DL_ADC12_getStatus(ADC12_0_INST)!=DL_ADC12_STATUS_CONVERSION_IDLE);
//暂停adc转换,否则会卡住
//获取存储在内存0的转换结果
DL_ADC12_stopConversion(ADC12_0_INST) ;
adc_data[0]=(DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0)*3.3f/4095.0f);
adc_data[1]=(DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_1)*3.3f/4095.0f);
//开启adc转换,因为单次转换会导致采集停止
DL_ADC12_enableConversions( ADC12_0_INST);
}
int main(void)
{
SYSCFG_DL_init();
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
uart0_send_string("adc test \r\n");
while (1)
{
adc_getvalue();
sprintf(str, "adc_value1 :%.2f adc_value2 :%.2f\r\n", adc_data[0],adc_data[1]);
//printf("adc_value :%.2f\r\n",adc_data);
uart0_send_string(str);
delay_ms(500);
}
}
中断单通道定时器触发(实现)



js
/*test ADC_interrupt*/
volatile bool ADC_Flag;//中断标志位
volatile uint16_t ADC_Val;//采样值
int main(void)
{
SYSCFG_DL_init();
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
NVIC_EnableIRQ(ADC12_0_INST_INT_IRQN);
uart0_send_string("ADC_interrupt test \r\n");
while (1)
{
ADC_Flag=false ;
DL_ADC12_startConversion(ADC12_0_INST);//开始采样变换
while(ADC_Flag==false);//等待转换完成,如果退出,说明已经弄好了
ADC_Val= DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);//读取结果
DL_ADC12_enableConversions(ADC12_0_INST);//再次使能中断,因为每次执行完就关闭了
sprintf(str, "adc_value1 :%.2f \r\n", ADC_Val*3.3f/4095.0f );
uart0_send_string(str);
delay_ms(500);
}
}
void ADC12_0_INST_IRQHandler (void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST))
{
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
ADC_Flag = true;
break;
default:
break;
}
}
DMA

天猛星最上面spi的基本引脚





1.电机驱动
jlc编码器(gpio外部中断

js
#ifndef __ENCODER_H_
#define __ENCODER_H_
#include "ti_msp_dl_config.h"
#include "main_IO.h"
typedef enum
{
FORWARD, // 正向
REVERSAL // 反向
} ENCODER_DIR;
typedef struct {
volatile long long temp_count; //保存实时计数值
int count; //根据定时器时间更新的计数值
ENCODER_DIR dir; //旋转方向
} ENCODER_RES;
void encoder_init(void);
int get_encoder_count(void);
ENCODER_DIR get_encoder_dir(void);
void encoder_update(void);
void GROUP1_IRQHandler(void);
#endif
#include "encoder.h"
static ENCODER_RES motor_encoder;
//编码器初始化
void encoder_init(void)
{
//编码器引脚外部中断
NVIC_ClearPendingIRQ(GPIOB_INT_IRQn);
NVIC_EnableIRQ(GPIOB_INT_IRQn);
}
//获取编码器的值
int get_encoder_count(void)
{
return motor_encoder.count;
}
//获取编码器的方向
ENCODER_DIR get_encoder_dir(void)
{
return motor_encoder.dir;
}
//编码器数据更新
//请间隔一定时间更新
void encoder_update(void)
{
motor_encoder.count = motor_encoder.temp_count;
//确定方向
motor_encoder.dir = ( motor_encoder.count >= 0 ) ? FORWARD : REVERSAL;
motor_encoder.temp_count = 0;//编码器计数值清零
}
//外部中断处理函数
void GROUP1_IRQHandler(void)
{
uint32_t gpio_status;
//获取中断信号情况
gpio_status = DL_GPIO_getEnabledInterruptStatus(GPIO_ENCODER_PORT, GPIO_ENCODER_PIN_A_PIN | GPIO_ENCODER_PIN_B_PIN);
//编码器A相上升沿触发
if((gpio_status & GPIO_ENCODER_PIN_A_PIN) == GPIO_ENCODER_PIN_A_PIN)
{
//如果在A相上升沿下,B相为低电平
if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_B_PIN))
{
motor_encoder.temp_count--;
}
else
{
motor_encoder.temp_count++;
}
}//编码器B相上升沿触发
else if((gpio_status & GPIO_ENCODER_PIN_B_PIN)==GPIO_ENCODER_PIN_B_PIN)
{
//如果在B相上升沿下,A相为低电平
if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN))
{
motor_encoder.temp_count++;
}
else
{
motor_encoder.temp_count--;
}
}
//清除状态
DL_GPIO_clearInterruptStatus(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN|GPIO_ENCODER_PIN_B_PIN);
}
输入捕获
js
/*左电机的编码器测速*/
void TIMG7_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(ENCODER_L_INST))
{
case DL_TIMERG_IIDX_CC0_DN:
uint16_t L_run_dierct = DL_GPIO_readPins(GPIO_Encoder_PORT, GPIO_Encoder_PIN_Front_Left_B_PIN);//读取IO电平获取电机旋转方向
if(L_run_dierct) control.speed.left_encoder_run--;
else control.speed.left_encoder_run++;
break;
default:
break;
}
}
/*右电机的编码器测速*/
void TIMG12_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(ENCODER_R_INST))
{
case DL_TIMERG_IIDX_CC0_DN:
uint16_t R_run_dierct = DL_GPIO_readPins(GPIO_Encoder_PORT, GPIO_Encoder_PIN_Front_Right_B_PIN);//读取IO电平获取电机旋转方向
if(R_run_dierct) control.speed.right_encoder_run--;
else control.speed.right_encoder_run++;
break;
default:
break;
}
}