打印
[CW32F030系列]

【CW32F030CxTx StartKit开发板】串口记录仪原型开发作品

[复制链接]
3180|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 jobszheng 于 2024-7-8 02:13 编辑

概述

数据记录在各行各业应用非常广泛。大到飞机飞行记录的黑闸子,小到我们DIY时的数据交互记录。本次DIY活动利用上市公司武汉芯源的CW32F030C8-startkit开发板设计实现一台串口数据记录仪,实现串口数据,RS485,Modbus协议等通讯环境下的数据记录功能。

架构框图

模块实现

Usart发送与中断接收

在我的项目经验下,我常常使用Usart的中断接收功能,一来,设计起来相对容易;二来,可不依赖硬件,比如某些型号的Usart2外设没有DMA超时中断等。今天给大家带来的实验是CW32F030的Usart1外设与板载CH340进行串口通讯。

使用武汉力源提供的外设驱动库 , 我们非常方便的调用了API就实现了串口初始化、配置、发送与接收功能。

我们依次配置RCC外设,GPIO外设,Usart外设,并打开Usart1_irqn的中断即可。示例代码如下:

static void uart_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA, ENABLE);
    RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_UART1, ENABLE);

    PA08_AFx_UART1TXD();
    PA09_AFx_UART1RXD();
    GPIO_InitStructure.Pins = GPIO_PIN_8;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = GPIO_PIN_9;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_Over = USART_Over_16;
    USART_InitStructure.USART_Source = USART_Source_PCLK;
    USART_InitStructure.USART_UclkFreq = 64000000;
    USART_InitStructure.USART_StartBit = USART_StartBit_FE;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(CW_UART1, &USART_InitStructure);

    NVIC_SetPriority(UART1_IRQn, 0);
    NVIC_EnableIRQ(UART1_IRQn);

    USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);
}

static void uart_blocksend(const char *ch, uint16_t len)
{
  uint16_t i;
  for (i = 0; i < len; i++)
  {
    USART_SendData_8bit(CW_UART1, debug_tx_buf[i]);
    while (USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) == RESET)
      ;
  }
}

void UART1_IRQHandler(void)
{
  uint8_t dat;
  if (USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
  {
    dat = USART_ReceiveData_8bit(CW_UART1);
    USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
    if (debug_rx_len < DEBUG_BUF_LEN)
    {
      debug_rx_buf[debug_rx_len] = dat;
      debug_rx_len++;
    }
  }
}

int main(void)
{
  bsp_init();

  InitTick(64000000);

  while(1)
  {
    if(time_line_ms == 0)
    {
      time_line_ms = 500;
      PB08_TOG();
      PB09_TOG();
      log_printf("hello 21ic, hello CW32F030\r\n");
    }
  }
}

我们通过串口助手工具软件来查看一下输出数据:

对于接收Rx方向,我们本次DIY要实现一个Modbus-RTU从站协议。因此,帧尾的判定我们使用定时器超时的方式处理。定时器我们使用上一个实验中的Systick定时器的1ms计数器来实现。

总结

CW32F030C8的usart外设满足基本需求,在工业控制领域完全胜任。当然,在消费领域肯定就更没有问题了。

I2C外设阻塞式读取CW24C02

EEPROM在项目中的应用非常广泛,因为其非易失性的特性,多用来保存配置参数信息等类似常用,但又有修改需求的数据。

我们本次实验还是使用武汉力源提供的外设驱动库来实现。毕竟是自家的EEPROM,兼容性特别的好,又稳定又可靠!I2C1外设的源代码如下:

int main(void)
{
  uint32_t i;
  bsp_init();

  InitTick(64000000);

  log_printf("Hello 21ic, Hello CW32F030\r\n");
  while (1)
  {
    if (time_line_ms == 0)
    {
      time_line_ms = 500;
      PB08_TOG();
      PB09_TOG();
    }
    if ((g_flag & 0x01) == 0x01)
    {
      g_flag &= ~(0x01);
      I2C_MasterWriteEepromData(CW_I2C1, WRITEADDRESS, tx_buf, 8);
      log_printf("EEPROM write:\r\n");
      for (i = 0; i < 8; i++)
      {
        log_printf("%02X ", tx_buf[i]);
      }
      log_printf("\r\n");
    }

    if ((g_flag & 0x04) == 0x04)
    {
      g_flag &= ~(0x04);
      I2C_MasterReadEepomData(CW_I2C1, WRITEADDRESS, rx_buf, 8);
      log_printf("EEPROM read:\r\n");
      for (i = 0; i < 8; i++)
      {
        log_printf("%02X ", rx_buf[i]);
      }
      log_printf("\r\n");
    }
  }
}

在本次DIY项目中,我们仅在上电初期读取一下EEPROM中的配置参数,不涉及到频繁读取与写入等需求,因此,我采用阻塞的方式来实现。


总结

得益于武汉力源工作师开发、分享的固件库,我们在实现I2C外设,读取EEPROM时几乎没有遇到困难。I2C外设以状态机的形态来实现,状态转移又清晰又利于编程实现。总之,比某国外32的MCU的I2C不知要好上多少倍?!

SPI Flash写入数据

在低成本的MCU,尤其是Cortex-M0+内核的MCU,其中,对外通讯速率最快的外设便是SPI外设接口。武汉力源CW32F030C8芯片的SPI外设的最大通讯速率为PCLK/4,即64MHz/4 = 16MHz,并且支持4b - 16b模式。这样的灵活参数配置对我们项目开发选择,软件设计带来极大的便利性。

言归正传,我们回到我们本次DIY项目中来,对于大容量的SPI Flash,我们使用它来存储数据,比如日志文件等。SPI Flash的容量大,并且相较于EEPROM的I2C总线,SPI的速率也较高。本次实验实现我们依然借助武汉力源的外设驱动库,源代码如下:

int main(void)
{
  uint32_t i;
  uint32_t flash_addr = 0;
  bsp_init();

  InitTick(64000000);

  // DeviceID = SPI_FLASH_DeviceID();
  // ManufactDeviceID = SPI_FLASH_ManufactDeviceID();
  // JedecID = SPI_FLASH_JedecID();
  // SPI_FLASH_UniqueID(UniqueID);
  // log_printf("\r\nDeviceID = 0x%X\r\nManufactDeviceID = 0x%X\r\nJedecID = 0x%X", DeviceID, ManufactDeviceID, JedecID);
  // log_printf("\r\nUniqueID = 0x");
  // for (i = 0; i < 8; i++)
  // {
  //   log_printf("%X", UniqueID[i]);
  // }

  // if (JedecID == sJedecID)
  // {
  //   log_printf("\r\n\nFLASH Detected\r\n");
  // }

  SPI_FLASH_SectorErase(flash_addr);

  for(i = 0; i < 64; i++)
  {
    tx_buf[i] = i;
  }
  SPI_FLASH_BufferWrite(tx_buf, flash_addr, 64);

  SPI_FLASH_BufferRead(rx_buf, flash_addr, 64);

  log_printf("flash read\r\n");
  for (i = 0; i < 64; i++)
  {
    log_printf("%02X ", rx_buf[i]);
  }
  while (1)
  {
    if (time_line_ms == 0)
    {
      time_line_ms = 500;
      PB08_TOG();
      PB09_TOG();
      // log_printf("hello world\r\n");
    }
  }
}

又是一次轻松实现!示例是通过清除sector,写入64字节的内容,再回读,结果如图所示:

总结

SPI主模式下发送时,由主机发起SCK。注意,在主机回读时,依然要发起一次发送数据,在这次sck驱动下,从机才会把数据回传回到主机,但这次主要发送的数据将会被从机丢掉而已。

CRC-16硬件实现

我们都知道Modbus-RTU协议中每帧数据都有crc-16校验,以保证传输命令的正确性,因为多应用于工业控制领域,一条错误的命令,机器的执行动作可能将导致严重的后果。在武汉力源芯片之前,我会在我的软件代码里面非常熟练的实现CRC-16的校验算法,为了加快计算,我还会把与或值以查询表的形式实现,以求空间换时间,但现在使用武汉力源的CW32F030后,我只需要写入CRC模块寄存器值即可,其会自动输出校验结果。方便快捷!其代码也只有这一行,而实现也才8行而已!

uint16_t CRC16_Calc_8bit(uint8_t CrcMode, uint8_t *pByteBuf, uint16_t ByteCnt)
{

    CW_CRC->CR = CrcMode;
    while (ByteCnt)
    {
        CW_CRC->DR8 = *pByteBuf;
        pByteBuf++;
        ByteCnt--;
    }
    return (CW_CRC->RESULT16);
}

总结

这个CRC校验模块不仅包含了我最常用的CRC-16 Modbus算法,还有其它7种算法。当然,模块也有CRC32算法。

串口记录仪原型开发作品

前面我们完成了本次DIY作品的各个模块的实现,现在我们依据我们的串口记录仪的原型开发方案来实现我们工程。

引脚分配

本次DIY活动使用的武汉芯源CW32F030-startkit开发板,引脚分配非常灵活,再加上芯片本身的IO复用功能非常强大。所以,我们本次的引脚分布如下:

PA06,UART2_TXD

PA07.UART2_RXD

PA08.UART1_TXD

PA09,UART1_RXD

PB06,I2C1_SCL

PB07,I2C1_SDA

PB12,SPI1_CS

PB13,SPI1_SCK

PB14,SPI1_MISO

PB15,SPI1_MOSI

Modbus-RTU协议

Modbus协议在工业控制领域应用非常广泛,其协议简单,严谨,包含指令,数据及CRC校验等。在工业控制领域中,Modbus协议部署的设备数量最多,可以说是事实的工业标准。

本次我们不在这里讲解Modbus的全部实现,仅讲解Modbus协议的CRC实现。对于Modbus-RTU协议,每帧数据以CRC-16结尾来验证此帧数据是否在传输中出现错误。错误则忽略此帧,等待主站对此帧的重传。以前,我在实现modbus-rtu-RTU时,只能软件来实现,如下图所用算法,但现在我可以使用硬件模块来实现,我甚至将其实现在中断中,毕竟只有一个PCLK的时延——系统肯定可以接受,这个功能极大改善了我的软件代码的质量。

static const UCHAR aucCRCHi[] = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40
};

static const UCHAR aucCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
    0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
    0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
    0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
    0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
    0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
    0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
    0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
    0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
    0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
    0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
    0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
    0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
    0x41, 0x81, 0x80, 0x40
};

USHORT
usMBCRC16( UCHAR * pucFrame, USHORT usLen )
{
    UCHAR           ucCRCHi = 0xFF;
    UCHAR           ucCRCLo = 0xFF;
    int             iIndex;

    while( usLen-- )
    {
        iIndex = ucCRCLo ^ *( pucFrame++ );
        ucCRCLo = ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] );
        ucCRCHi = aucCRCLo[iIndex];
    }
    return ( USHORT )( ucCRCHi << 8 | ucCRCLo );
}

EEPROM中存储匹配参数

在上电初期,我们首先从EEPROM中读取配置参数与过滤规则,比如我们仅保留Modbus主站发出来的ID为0x01的帧数据。做为示例,我们将过滤规则定义如下数据结构:

typedef struct mb_filter_s
{
    uint8_t id_mask;
    uint8_t func_id_mask;
    uint8_t len_mask;
    uint8_t err_mask;
}mb_filter_t;

SPI Flash的日志数据存储

在本次DIY采用的是直接写入模式。限于时间关系,未实现文件系统,但8KB的SRAM及64KB的Flash容量实现文件系统足矣。

不过,虽然没有实现文件系统,但我们仍然要实现一个日志的info头数据

typedef struct log_info_s
{
  uint8_t flag; // fixed to 0xA5;
  uint8_t len;
  uint8_t next;
  uint8_t mode;
}log_info_t;

视频展示

使用特权

评论回复
沙发
chenjun89| | 2024-7-9 08:27 | 只看该作者
可以用开源的shell终端

使用特权

评论回复
板凳
AdaMaYun| | 2024-7-9 11:24 | 只看该作者
其实就是串口通讯,有相关协议的开发方式嘛

使用特权

评论回复
地板
梅花香自123| | 2024-8-22 16:21 | 只看该作者
如何配置GPIO和USART外设。

使用特权

评论回复
5
jobszheng|  楼主 | 2024-8-22 20:25 | 只看该作者
梅花香自123 发表于 2024-8-22 16:21
如何配置GPIO和USART外设。

啥情况!?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:嵌入式技术专家
简介:热爱开源,乐于分享。在嵌入式技术领域里面,主攻通讯协议,Modbus,TCP/IP以及虚拟化和RTOS

16

主题

402

帖子

2

粉丝