前言

大家好,在上一期我们主要介绍了关于家庭能源网关这个项目的选材。本期我们讲的是如何实现项目主控MCU华大HC32F005的UART中断配置以及发送和接收数据,为我们后续项目读取测量到的电流、电压、功率做准备。

大家有需要可以自行下载有关HC32F005demo,例程包含了所有外设的初始化以及配置。手册在上一章下载,或者去官网下载,这里就不过多介绍HC32F005其他资源。重点介绍UART这部分。

UART介绍

HC32F005有 2 个通用 UART 模块(UART0/1),支持半双工和全双工传输;支持 8bit、 9bit 数据格式;支持 Mode0/1/2/3 四种不同传输模式;UART0 的波特率由 TIMER0 产生,UART1 的波特率由 TIMER1 产生;支持多机通讯模式;支持自动地址识别; 支持给定地址和广播地址。 通用 UART(UART0/1)只有一个时钟输入 PCLK,寄存器配置逻辑和数据收发逻辑 都工作在该时钟域。如下图是UART结构框图:

家庭能源网关开发历程(二)JSY-MK-163串口通讯调试过程_工作模式

UART工作模式

Mode0(同步模式,半双工)

当工作在 Mode0 时,UART 工作在同步模式,其波特率为固定的 PCLK 时钟的 1/12。 UART 接收数据由 RXD 输入、UART 发送数据有 RXD 输出,RXD 此时为输入输 出端口。UART 同步移位时钟由 TXD 输出,TXD 此时为输出端口。注意,本模式只 能作为主机发送同步移位时钟,不可以作为从机从外部接收该时钟。该模式下,传输 的数据位宽只能是 8 位的,没有起始位和结束位。 将 UARTx_SCON.SM0 和 UARTx_SCON.SM1 清零,可进入 Mode0 工作模式。

Mode1(异步模式,全双工)

当工作在 Mode1 时,发送数据通过 TXD 发送,接收数据通过 RXD 接收。该数据 由 10 位组成:起始位“0”开始,紧接着 8 位数据位(低位在先,高位在后),最后是结 束位“1”。 该模式下,波特率由定时器模块产生,并且是可编程的。UART0 的波特率由 TIMER0 产生,UART1 的波特率由 TIMER1 产生。 将 UARTx_SCON.SM0 清 0,UARTx_SCON.SM1 置 1,可进入 Mode1 工作模式。

Mode2(异步模式,全双工)

当工作在 Mode2 时,发送数据通过 TXD 发送,接收数据通过 RXD 接收。该数据 由 11 位组成:起始位“0”开始,接着是 8 个数据位,1 个 TB8 位和结束位。额外的 TB8 位是用来在多机通讯环境下使用,当 TB8=1,表明所接收的是地址帧;当 TB8=0, 表明所接收的是数据帧。当不需要多机通讯时,此位也可以作为奇偶校验位来使用。 该模式下,波特率可以独立产生,不需要外部 TIMER 产生。 将 UARTx_SCON.SM0 置 1,UARTx_SCON.SM1 清 0,可进入 Mode2 工作模式。

Mode3(异步模式,全双工)

Mode3 的数据格式,传输时序以及操作方式都与 Mode2 相同,唯一的区别是 Mode3 的波特率由 TIMER 产生,而不是像 Mode2 由设备自己独立产生。Mode3 的波特率 是可编程的,波特率生成方式与 Mode1 相同。本产品中,UART0 的波特率由 TIMER0 产生,UART1 的波特率由 TIMER1 产生。 将 UARTx_SCON.SM0 置 1,UARTx_SCON.SM1 置 1,可进入 Mode3 工作模式。 

注:选用哪种工作模式根据我们用哪个UART决定,不可以随便用,否则无法收发数据

UART波特率配置

Mode0

当工作在 Mode0 时,波特率被固定在 PCLK 的 1/12,不需要 TIMER 的支持。

Mode1/3

当工作在 Mode1 或者 Mode3 时,波特率由 TIMER 的溢出时间决定。具体公式如下图所示:

BaudRate = (UARTx_SCON.DBAUD+1) * Freq/32 * (65536 - TM)

其中,UARTx_SCON.DBAUD 表示双倍波特率,Freq 为 PCLK 时钟频率,TM 为 TIMER 计数值。注意,TIMER 必须配置为 16 位自动重载入模式,计数寄存器和重载 寄存器都得写入 TM 值。

Mode2

当工作在 Mode2 时,波特率被固定在如下公式所得值:

BaudRate = (UARTx_SCON.DBAUD+1) * Freq/64

其中,UARTx_SCON.DBAUD 表示双倍波特率,Freq 为 PCLK 时钟频率。

帧错误检测

Mode1/2/3 具有帧错误检测功能,硬件会自动检测接收到的帧数据是否带有效的 Stop 位。如果没有收到有效 Stop 位,则 UARTx_ISR.FE 置 1。UARTx_ISR.FE 位由硬件 置 1,软件清 0,如果软件未及时清 0,则后续收到数据即使带有效 Stop 位,也不会 把 UARTx_ISR.FE 标志清 0。

多机通讯

Mode2/3 具有多机通讯功能,为此在其帧格式中增加了 1 位 TB8/RB8。将 UARTx_SCON.SM2 置“1”,可开启多机通讯位。

当开启多机通讯位后,发送数据时,主机可以通过 UARTx_SCON.TB8 来区分当前帧 是地址帧(UARTx_SCON.TB8=1)还是数据帧(UARTx_SCON.TB8=0)。接收数据时, 从机会忽略 RB8 位(第 9 位)为“0”的当前接收帧。当收到帧的 RB8 位(第 9 位) 为“1”表明其是地址帧,从机会继续判断接收到的地址与其自身地址是否相等。如果 匹配,则从机会对 UARTx_SCON.RB8 置“1”,并对 UARTx_ISR.R I 置“1”,以表明 该帧为地址帧并且地址已经匹配。从机软件看到 UARTx_SCON.RB8=1 并且 UARTx_ISR.RI=1 后,先把 UARTx_SCON.SM2 位清“0”,然后准备接受给它的数据 帧。如果地址不等,表明主机并不是寻址该从机,从机硬件保持 UARTx_SCON.RB8 和 UARTx_ISR.RI 为“0”,软件保持 UARTx_SCON.SM2 位为“1”,从机继续处于地址 监听状态。

UART中断配置流程

1.串口引脚初始化

void App_PortInit(void)
{
    stc_gpio_cfg_t stcGpioCfg;
    DDL_ZERO_STRUCT(stcGpioCfg);
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //使能GPIO模块时钟
    ///<TX
    stcGpioCfg.enDir = GpioDirOut;
    Gpio_Init(GpioPort2, GpioPin3, &stcGpioCfg);
    Gpio_SetAfMode(GpioPort2, GpioPin3, GpioAf6);          //配置P23 端口为URART0_TX
    ///<RX
    stcGpioCfg.enDir = GpioDirIn;
    Gpio_Init(GpioPort2, GpioPin4, &stcGpioCfg);
    Gpio_SetAfMode(GpioPort2, GpioPin4, GpioAf6);          //配置P24 端口为URART0_RX
}

2.串口波特率设置

void UartBaudCfg_Init(void)
{
    uint16_t timer=0;
    stc_uart_baud_cfg_t stcBaud;
    stc_bt_cfg_t stcBtCfg;
	 stc_uart_cfg_t  stcCfg;
    DDL_ZERO_STRUCT(stcBaud);
    DDL_ZERO_STRUCT(stcBtCfg);
	DDL_ZERO_STRUCT(stcCfg);
    //外设时钟使能
    Sysctrl_SetPeripheralGate(SysctrlPeripheralBt,TRUE);//模式0/2可以不使能
    Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1,TRUE)
    stcBaud.bDbaud  = 1u;//双倍波特率功能
    stcBaud.u32Baud = 9600;//更新波特率位置
    stcBaud.enMode  = UartMode1; //计算波特率需要模式参数
    stcBaud.u32Pclk = Sysctrl_GetPClkFreq(); //获取PCLK
    timer = Uart_SetBaudRate(M0P_UART1, &stcBaud);
    stcBtCfg.enMD = BtMode2;
    stcBtCfg.enCT = BtTimer;
    Bt_Init(TIM1, &stcBtCfg);//调用basetimer1设置函数产生波特率
    Bt_ARRSet(TIM1,timer);
    Bt_Cnt16Set(TIM1,timer);
    Bt_Run(TIM1);
		stcCfg.enRunMode = UartMode1;//测试项,更改此处来转换4种模式测试
		Uart_Init(M0P_UART1, &stcCfg);
	}

 3.相关中断配置

 ///< UART中断配置
	Uart_EnableIrq(M0P_UART1, UartRxIrq);
    Uart_ClrStatus(M0P_UART1, UartRC);
	EnableNvic(UART1_IRQn, IrqLevel3, TRUE);

4.设置中断函数 

//UART1中断函数
void Uart1_IRQHandler(void)
{
    if(Uart_GetStatus(M0P_UART1, UartTC))         //UART1数据发送
    {
        Uart_ClrStatus(M0P_UART1, UartTC);        //清中断状态位
		fnUart_SendIR();//发送数据
    }
	if(Uart_GetStatus(M0P_UART1, UartRC))         //UART1数据接收
    {
        Uart_ClrStatus(M0P_UART1, UartRC);        //清中断状态位
        com_1.rbuf[com_1.rcnt] = Uart_ReceiveData(M0P_UART1);   //接收数据字节
		com_1.rcnt=(com_1.rcnt+1)%256;//更新计数接收
    }
} 

注意:到这里我们已经完成串口中断的配置流程,但是还不能实现串口的收发数据。还有个最重要的步骤要配置,那就是还要选择中断向量号,但是这个中断向量号并不是对应我们使用的哪个串口,具体选择哪个中断向量号,可参考如下图:

家庭能源网关开发历程(二)JSY-MK-163串口通讯调试过程_嵌入式硬件_02