文章目录
05.TMS570LC43入门指南——串口中断
一、简介
通过上节对串口的学习,我们学会了如何使用串口进行收发操作。通过本节的学习,将首次接触和使用 TMS570LC43
的中断。
通过这篇文章,你将学到以下内容:
- 如何使用
HALCoGen
开启中断 - 如何使用
SCI
(即串口) 中断 - 使用其他串口的一些注意事项
开发平台:
- Windows x64
- TMS570LC43开发板
二、项目实现
首先我们需要建立 CCS
以及 HALCoGen
工程文件,这里不再赘述,具体操作可以参考我前面的文章,链接如下:
欢迎各位大佬阅读。
其次我们需要认识一下 TMS570LC43
的中断管理器。具体信息可以参考数据手册(官方链接,可放心跳转): ti.com/lit/ds/spns195c/spns195c.pdf。我这里进行简单的总结。
TMS570LC43
有两个片上矢量中断管理器(VIM)模块。VIM模块提供硬件辅助,用于对设备上存在的许多中断源进行优先级排序和控制。中断是由程序执行正常流程之外的事件引起的。通常,这些事件需要CPU及时响应;因此,当中断发生时,CPU将执行从正常程序流切换到中断服务例程(ISR)。
VIM模块具有以下特性:
- 支持128个中断通道
- 为请求线路提供可编程优先级
- 通过屏蔽管理中断通道
- 将中断通道优先分配给CPU
- 每次中断时向CPU提供ISR(中断服务程序)的地址
两个VIM模块是同步的。这两个VIM模块被内存映射到相同的地址空间。从程序员模型的角度来看,它只是一个VIM模块。对VIM1寄存器和内存的写入将广播到VIM1和VIM2。从VIM1读取将只读取VIM1寄存器和内存。所有发送到VIM1模块的中断请求也将发送到VIM2模块。
由于VIM1和VIM2具有相同的设置,因此它们都将导致响应相同中断请求的相同输出行为。第二个VIM模块充当诊断检查器模块。两个VIM模块的输出信号被路由到 CCM-R5F
模块,并不断进行比较。检测到的错误比较将作为错误信号发送给 ESM
模块。
本节内容,我们就将学习使用 SCI
的中断。
2.1 硬件部分
对于硬件具体的介绍,可以参考我前一篇文章,在本章中,硬件设备均一致,链接为:04.TMS570LC43入门指南——串口操作。
另外,补充上一篇的相关内容。如果需要使用引脚输出的串口,需要使用串口转USB模块,常见的为CH340G模块,某宝上很便宜,有条件的大佬也可自己制作工具。
2.2 软件部分——接收中断
2.2.1 HALCoGen 配置
上节中讲到的基本配置我就不再赘述,我这里之讲解后续步骤。最好阅读一下我上节所讲的配置方法,然后接着根据本文档配置!!!
-
在
SCI -> SCI Global
中开启串口接收中断 -
在
TMS570LC4357ZWT -> VIM Channel 0-31
中勾选LIN1 High
: -
配置完上述功能后,即可生成程序。依旧使用快捷键
F5
或依次点击File -> Generate Code
。
2.2.2 CCS 配置
按照上述步骤后,生成文件,依旧在 CCS
中进行编写逻辑代码的操作。还是先找到我们的 HL_sys_main.c
文件。这里我给出示例代码:
/* USER CODE BEGIN (0) */
/* USER CODE END */
/* Include Files */
#include "HL_sys_common.h"
/* USER CODE BEGIN (1) */
#include "HL_system.h"
#include "HL_sci.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END */
/** @fn void main(void)
* @brief Application main function
* @note This function is empty by default.
*
* This function is called after startup.
* The user can use this function to implement the application.
*/
/* USER CODE BEGIN (2) */
#define BUF_MAX_SIZE (1024U) // 缓冲区最大大小
/* SCI 状态宏定义 */
#define SCI_RXIDLE ((uint8)(0x00)) // SCI 空闲状态
#define SCI_RXD ((uint8)(0x01)) // SCI 接收到 0x0D
#define SCI_RXEND ((uint8)(0x02)) // SCI 接收完成
/* SCI 结构体 */
typedef struct
{
uint8 rxstatus; // SCI 接收状态
uint16 rxlen; // 接收数据长度
uint8 rxBuf[BUF_MAX_SIZE]; // 接收缓冲区
}SCI_STR;
SCI_STR sciStr;
/* USER CODE END */
int main(void)
{
/* USER CODE BEGIN (3) */
_enable_interrupt_(); // 使能中断
sciInit();
sciStr.rxstatus = SCI_RXIDLE;
while(1)
{
if (sciStr.rxstatus == SCI_RXEND) {
sciSend(sciREG1, sciStr.rxlen, sciStr.rxBuf);
sciStr.rxstatus = SCI_RXIDLE;
memset(&sciStr.rxBuf[0], 0, sciStr.rxlen);
sciStr.rxlen = 0;
}
}
/* USER CODE END */
return 0;
}
/* USER CODE BEGIN (4) */
/*
* @brief : 串口通知函数
* @param : [sci]: sci端口寄存器
* [flags]: 中断标志
* @return : void
* @author : Liu Jiahao
* @date : 2024-03-26
* @version : v1.1
* @copyright : Copyright By Liu Jiahao, All Rights Reserved
*/
void sciNotification(sciBASE_t *sci, uint32 flags)
{
switch (flags)
{
/* 接收中断 */
case SCI_RX_INT:
{
uint8 dat = (uint8)(sci->RD & 0x000000FFU);
if (sciStr.rxlen < BUF_MAX_SIZE) {
sciStr.rxBuf[sciStr.rxlen] = dat;
sciStr.rxlen++;
if ((dat == (uint8)(0x0D)) && (sciStr.rxstatus != SCI_RXD)) {
sciStr.rxstatus = SCI_RXD;
}
else if ((dat == (uint8)(0x0A)) && sciStr.rxstatus == SCI_RXD) {
sciStr.rxstatus = SCI_RXEND;
}
else {
sciStr.rxstatus = SCI_RXIDLE;
}
}
} break;
default: break;
}
}
/* USER CODE END */
同时,如果要实现该功能,还需要更改生成的函数。由于我们之前在 HALCoGen
中勾选了 LIN1 High
中断。此时我们可以看到 HL_sci.c
文件中多了一个函数 lin1HighLevelInterrupt
,这是其中断服务函数,而我们需要做出如下修改:
/* SourceId : SCI_SourceId_022 */
/* DesignId : SCI_DesignId_017 */
/* Requirements : HL_CONQ_SCI_SR20, HL_CONQ_SCI_SR21 */
/** @fn void lin1HighLevelInterrupt(void)
* @brief Level 0 Interrupt for SCI1
*/
#pragma CODE_STATE(lin1HighLevelInterrupt, 32)
#pragma INTERRUPT(lin1HighLevelInterrupt, IRQ)
void lin1HighLevelInterrupt(void)
{
uint32 vec = sciREG1->INTVECT0;
/******************************** 注释以下内容 **********************************/
// uint8 byte;
/* USER CODE BEGIN (28) */
/* USER CODE END */
switch (vec)
{
case 1U:
sciNotification(sciREG1, (uint32)SCI_WAKE_INT);
break;
case 3U:
sciNotification(sciREG1, (uint32)SCI_PE_INT);
break;
case 6U:
sciNotification(sciREG1, (uint32)SCI_FE_INT);
break;
case 7U:
sciNotification(sciREG1, (uint32)SCI_BREAK_INT);
break;
case 9U:
sciNotification(sciREG1, (uint32)SCI_OE_INT);
break;
case 11U:
/* receive */
/******************************** 增加以下内容 **********************************/
sciNotification(sciREG1, (uint32)SCI_RX_INT);
/******************************** 注释以下内容 **********************************/
// byte = (uint8)(sciREG1->RD & 0x000000FFU);
//
// if (g_sciTransfer_t[0U].rx_length > 0U)
// {
// *g_sciTransfer_t[0U].rx_data = byte;
// g_sciTransfer_t[0U].rx_data++;
//
// g_sciTransfer_t[0U].rx_length--;
// if (g_sciTransfer_t[0U].rx_length == 0U)
// {
// sciNotification(sciREG1, (uint32)SCI_RX_INT);
// }
// }
break;
case 12U:
/* transmit */
/*SAFETYMCUSW 30 S MR:12.2,12.3 <APPROVED> "Used for data count in Transmit/Receive polling and Interrupt mode" */
--g_sciTransfer_t[0U].tx_length;
if (g_sciTransfer_t[0U].tx_length > 0U)
{
uint8 txdata = *g_sciTransfer_t[0U].tx_data;
sciREG1->TD = (uint32)txdata;
g_sciTransfer_t[0U].tx_data++;
}
else
{
sciREG1->CLEARINT = SCI_TX_INT;
sciNotification(sciREG1, (uint32)SCI_TX_INT);
}
break;
default:
/* phantom interrupt, clear flags and return */
sciREG1->FLR = sciREG1->SETINTLVL & 0x07000303U;
break;
}
/* USER CODE BEGIN (29) */
/* USER CODE END */
}
经过上述操作,我们就可以使用串口中断了。但是发送字节的末尾要跟上换行符(即0x0D、0x0A)。我们先看看效果(依旧采用上节配置的 115200 8bits数据位 1bit停止位 无校验位):
接下来我将阐述上述代码。
首先,对于 HL_sys_main.c
文件,我们实现的功能是将收到的数据再次发送出去,并且用 换行符(即0x0D、0x0A) 进行判断接收是否结束。
其次,对于 HL_sci.c
文件,我们修改了中断服务函数,中断产生后,复位中断,并且发送 SCI_RX_INT
通知给 sciNotification
函数进行处理。修改中断服务函数是由于,原先的服务函数是需要结构体提前输入参数的,而输入参数就需要使用到其库中原本的函数 sciReceive
或 sciReceiveByte
,并不是这两个函数有问题,而是其输入参数需要写明需要接收的字节数,这对于实际情况而言,我们并不知道这一帧数据有多少个字节,所以其不够灵活,于是我将它注释掉了。
那么有的朋友会问了,sciNotification
这个函数在这个中断中我知道,那别的中断呢?其实,相信使用过 STM32 HAL 库 的朋友对这个都不陌生,其 HAL 库也采用类似于这种的中断->通知的方法。我们看到项目文件中的 source
目录下,存在一个叫做 HL_notification.c
的文件,这个文件里包含通知函数,有些朋友也喜欢将函数实现直接写在这里面,我是在外部进行了实现,两种方法均可。这里我粘贴出,HL_notification.c
中的SCI通知函数:
#pragma WEAK(sciNotification)
void sciNotification(sciBASE_t *sci, uint32 flags)
{
/* enter user code between the USER CODE BEGIN and USER CODE END. */
/* USER CODE BEGIN (32) */
/* USER CODE END */
}
这里提一句,自己的代码需要写在
USER CODE BEGIN
和USER CODE END
中间,否则下次使用HALCoGen
对该项目生成文件的时候会覆盖调你写的代码。
可以看到,其使用了 弱函数 声明,关于该解释可以跳转 _weak 弱函数-CSDN博客 进行阅读。我在这里进行简单介绍,带 weak
的函数和不带 weak
的函数可以同时存在,如果有不带 weak 的函数,就会优先链接不带 weak 的实现,如果没有找到不带 weak 的实现函数,就会使用 weak 函数作为默认的实现。
我这里再次推荐一篇不错的文,讲解有关 weak
的知识:
使用 weak(弱函数)构建跨平台的C语言代码 - 知乎 (zhihu.com)
- 如无法跳转,可以复制进行手动跳转: https://zhuanlan.zhihu.com/p/616109439
言归正传,我在 HL_sys_main.c
文件中重新定义了 sciNotification
函数并进行了实现。由此,我们就完成了有关 SCI 中断接收 的工作。如果之前有 stm32 HAL 库开发经验的朋友,相信对这部分理解起来还是很快的,不熟悉的朋友可以再次看看源码是如何实现的。
2.3 软件部分——发送中断
既然我们实现了接收中断,那么对应的发送中断也应当玩一玩,好处呢就是,发送数据的时候不需要在等待寄存器标志上浪费时间,那么你就可以让程序继续运行在主函数中。
为了方便,我们直接使用 2.2 中的配置,继续在此基础上进行增加。
2.3.1 HALCoGen 配置
在 SCI1 -> SCI Global
中勾选 RX INT
:
设置完成后,别忘了生成文件!!!
2.3.2 CCS 配置
同样,对上述代码进行修改,这里我给出 HL_sys_main.c
的示例:
/* USER CODE BEGIN (0) */
/* USER CODE END */
/* Include Files */
#include "HL_sys_common.h"
/* USER CODE BEGIN (1) */
#include "HL_system.h"
#include "HL_sci.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END */
/** @fn void main(void)
* @brief Application main function
* @note This function is empty by default.
*
* This function is called after startup.
* The user can use this function to implement the application.
*/
/* USER CODE BEGIN (2) */
#define BUF_MAX_SIZE (1024U) // 缓冲区最大大小
/* SCI 状态宏定义 */
#define SCI_RXIDLE ((uint8)(0x00)) // SCI 空闲状态
#define SCI_RXD ((uint8)(0x01)) // SCI 接收到 0x0D
#define SCI_RXEND ((uint8)(0x02)) // SCI 接收完成
#define SCI_TXIDLE ((uint8)(0x00)) // SCI 发送空闲状态
#define SCI_TXON ((uint8)(0x01)) // SCI 正在发送状态
/* SCI 结构体 */
typedef struct
{
uint8 rxstatus; // SCI 接收状态
uint16 rxlen; // 接收数据长度
uint8 rxBuf[BUF_MAX_SIZE]; // 接收缓冲区
uint8 txstatus; // SCI 发送状态
uint16 txlen; // 发送数据长度
uint8 txBuf[BUF_MAX_SIZE]; // 发送缓冲区
}SCI_STR;
SCI_STR sciStr;
/* USER CODE END */
int main(void)
{
/* USER CODE BEGIN (3) */
_enable_interrupt_(); // 使能中断
sciInit();
sciStr.rxstatus = SCI_RXIDLE;
sciStr.txstatus = SCI_TXIDLE;
while(1)
{
if ((sciStr.rxstatus == SCI_RXEND) && (sciStr.txstatus == SCI_TXIDLE)) {
memcpy(&sciStr.txBuf[0], &sciStr.rxBuf[0], sciStr.rxlen);
sciStr.txlen = sciStr.rxlen;
sciStr.txstatus = SCI_TXON;
sciSend(sciREG1, sciStr.txlen, sciStr.txBuf);
sciStr.rxstatus = SCI_RXIDLE;
memset(&sciStr.rxBuf[0], 0, sciStr.rxlen);
sciStr.rxlen = 0;
}
}
/* USER CODE END */
return 0;
}
/* USER CODE BEGIN (4) */
/*
* @brief : 串口通知函数
* @param : [sci]: sci端口寄存器
* [flags]: 中断标志
* @return : void
* @author : Liu Jiahao
* @date : 2024-03-26
* @version : v1.1
* @copyright : Copyright By Liu Jiahao, All Rights Reserved
*/
void sciNotification(sciBASE_t *sci, uint32 flags)
{
switch (flags)
{
/* 接收中断 */
case SCI_RX_INT:
{
uint8 dat = (uint8)(sci->RD & 0x000000FFU);
if (sciStr.rxlen < BUF_MAX_SIZE) {
sciStr.rxBuf[sciStr.rxlen] = dat;
sciStr.rxlen++;
if ((dat == (uint8)(0x0D)) && (sciStr.rxstatus != SCI_RXD)) {
sciStr.rxstatus = SCI_RXD;
}
else if ((dat == (uint8)(0x0A)) && sciStr.rxstatus == SCI_RXD) {
sciStr.rxstatus = SCI_RXEND;
}
else {
sciStr.rxstatus = SCI_RXIDLE;
}
}
} break;
/* 发送完成中断 */
case SCI_TX_INT:
{
sciStr.txstatus = SCI_TXIDLE;
memset(&sciStr.txBuf[0], 0, sciStr.txlen);
} break;
default: break;
}
}
/* USER CODE END */
同样,由于我们重新生成过代码,故需要重新在 HL_sci.c
中修改:
/* SourceId : SCI_SourceId_022 */
/* DesignId : SCI_DesignId_017 */
/* Requirements : HL_CONQ_SCI_SR20, HL_CONQ_SCI_SR21 */
/** @fn void lin1HighLevelInterrupt(void)
* @brief Level 0 Interrupt for SCI1
*/
#pragma CODE_STATE(lin1HighLevelInterrupt, 32)
#pragma INTERRUPT(lin1HighLevelInterrupt, IRQ)
void lin1HighLevelInterrupt(void)
{
uint32 vec = sciREG1->INTVECT0;
/******************************** 注释以下内容 **********************************/
// uint8 byte;
/* USER CODE BEGIN (28) */
/* USER CODE END */
switch (vec)
{
case 1U:
sciNotification(sciREG1, (uint32)SCI_WAKE_INT);
break;
case 3U:
sciNotification(sciREG1, (uint32)SCI_PE_INT);
break;
case 6U:
sciNotification(sciREG1, (uint32)SCI_FE_INT);
break;
case 7U:
sciNotification(sciREG1, (uint32)SCI_BREAK_INT);
break;
case 9U:
sciNotification(sciREG1, (uint32)SCI_OE_INT);
break;
case 11U:
/* receive */
/******************************** 增加以下内容 **********************************/
sciNotification(sciREG1, (uint32)SCI_RX_INT);
/******************************** 注释以下内容 **********************************/
// byte = (uint8)(sciREG1->RD & 0x000000FFU);
//
// if (g_sciTransfer_t[0U].rx_length > 0U)
// {
// *g_sciTransfer_t[0U].rx_data = byte;
// g_sciTransfer_t[0U].rx_data++;
//
// g_sciTransfer_t[0U].rx_length--;
// if (g_sciTransfer_t[0U].rx_length == 0U)
// {
// sciNotification(sciREG1, (uint32)SCI_RX_INT);
// }
// }
break;
case 12U:
/* transmit */
/*SAFETYMCUSW 30 S MR:12.2,12.3 <APPROVED> "Used for data count in Transmit/Receive polling and Interrupt mode" */
--g_sciTransfer_t[0U].tx_length;
if (g_sciTransfer_t[0U].tx_length > 0U)
{
uint8 txdata = *g_sciTransfer_t[0U].tx_data;
sciREG1->TD = (uint32)txdata;
g_sciTransfer_t[0U].tx_data++;
}
else
{
sciREG1->CLEARINT = SCI_TX_INT;
sciNotification(sciREG1, (uint32)SCI_TX_INT);
}
break;
default:
/* phantom interrupt, clear flags and return */
sciREG1->FLR = sciREG1->SETINTLVL & 0x07000303U;
break;
}
/* USER CODE BEGIN (29) */
/* USER CODE END */
}
那我,我们先运行之后看看效果:
大家一定不要忘记,发送的时候勾选上发送新行。由此,发送中断完成,下面我将解释上述代码。
首先,对于 HL_sys_main.c
文件,我依旧使用了 sciNotification
通知函数进行解析,大家可以看到,我对 SCI_TX_INT
的注释是 发送完成中断。为什么这样写呢,那是因为,大家可以看到 HL_sci.c
中的 lin1HighLevelInterrupt
函数中对于发送中断的处理,这部分写的还是比较好的,大家可以放心使用。我们在发送的时候只需要调用 sciSend
或 sciSendByte
即可,TI对于这部分的代码做的还不错,所以我也没有进行修改。我们可以看到,其操作是,发送完成后才通知 SCI_TX_INT
标志,故我注释的是 发送完成中断。
三、一些小问题
3.1使用 SCI2、SCI3、SCI4的配置问题
我们使用 SCI1
的时候,它只需要我上述的简单配置,但对于其他串口,我们需要在 HALCoGen
中勾选 PINMUX
才能正确输出,如下所示:
大家可以按照需要进行勾选,这也是大多数人明明配置好SCI但引脚没有输出的原因!!!
3.2 必须要使能中断
大家可以看到我上述的代码中,在 main
函数中的第一句是:
_enable_interrupt_(); // 使能中断
这一句是必要的,否则中断不生效!!!
3.3 串口中断线优先级
串口中断的时候,其实有两个优先级可以选择,我这里说的不是总中断里面的优先级,总中断里面的优先级可以自定义,而串口里面自带两个优先等级供选择:
大家可以看到分为 Low Level
和 High Level
,通常默认情况下都是 High Level
,其在 VIM
中如下所示:
上图来源为数据手册:ti.com/lit/ds/spns195c/spns195c.pdf
在 HALCoGen
中分别为 LIN1 High
和 LIN1 Low
,大家可以自行查看。
四、写在最后
本文介绍了 如何在TMS570LC43上进行串口中断收发的操作以及一些配置的小问题。另外,该方法不建议用在具体项目中,否则每一帧数据都要加帧结束的判定。
在后续的文章中,将继续对 TMS570LC43x 进行详细的入门指导,欢迎读者关注!!!
目前暂时没有考虑整合的打算,所以各位读者如果需要看别的教程,可以点进 专栏 进行查找。在后续的更新中,将会逐步加入各个文章的链接,以便大家快速翻阅。另外源码会逐步开源。
欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!
另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。
-
个人CSDN账号:刘梓谦_-CSDN博客
-
GitHub:Jiahao-Liu29 (github.com)