[CW32F030系列] 【CW32F030CxTx StartKit开发板】串口记录仪原型开发作品

[复制链接]
 楼主| jobszheng 发表于 2024-7-8 02:05 | 显示全部楼层 |阅读模式
本帖最后由 jobszheng 于 2024-7-8 02:13 编辑

概述

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

架构框图
框图.png
模块实现

Usart发送与中断接收

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

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

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

  1. static void uart_init(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStructure;
  4.     USART_InitTypeDef USART_InitStructure;

  5.     RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOA, ENABLE);
  6.     RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_UART1, ENABLE);

  7.     PA08_AFx_UART1TXD();
  8.     PA09_AFx_UART1RXD();
  9.     GPIO_InitStructure.Pins = GPIO_PIN_8;
  10.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  11.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  12.     GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
  13.     GPIO_InitStructure.Pins = GPIO_PIN_9;
  14.     GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  15.     GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

  16.     USART_InitStructure.USART_BaudRate = 115200;
  17.     USART_InitStructure.USART_Over = USART_Over_16;
  18.     USART_InitStructure.USART_Source = USART_Source_PCLK;
  19.     USART_InitStructure.USART_UclkFreq = 64000000;
  20.     USART_InitStructure.USART_StartBit = USART_StartBit_FE;
  21.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  22.     USART_InitStructure.USART_Parity = USART_Parity_No ;
  23.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  24.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  25.     USART_Init(CW_UART1, &USART_InitStructure);

  26.     NVIC_SetPriority(UART1_IRQn, 0);
  27.     NVIC_EnableIRQ(UART1_IRQn);

  28.     USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);
  29. }

  30. static void uart_blocksend(const char *ch, uint16_t len)
  31. {
  32.   uint16_t i;
  33.   for (i = 0; i < len; i++)
  34.   {
  35.     USART_SendData_8bit(CW_UART1, debug_tx_buf[i]);
  36.     while (USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) == RESET)
  37.       ;
  38.   }
  39. }

  40. void UART1_IRQHandler(void)
  41. {
  42.   uint8_t dat;
  43.   if (USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
  44.   {
  45.     dat = USART_ReceiveData_8bit(CW_UART1);
  46.     USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
  47.     if (debug_rx_len < DEBUG_BUF_LEN)
  48.     {
  49.       debug_rx_buf[debug_rx_len] = dat;
  50.       debug_rx_len++;
  51.     }
  52.   }
  53. }

  54. int main(void)
  55. {
  56.   bsp_init();

  57.   InitTick(64000000);

  58.   while(1)
  59.   {
  60.     if(time_line_ms == 0)
  61.     {
  62.       time_line_ms = 500;
  63.       PB08_TOG();
  64.       PB09_TOG();
  65.       log_printf("hello 21ic, hello CW32F030\r\n");
  66.     }
  67.   }
  68. }

我们通过串口助手工具软件来查看一下输出数据:
格式工厂 屏幕录像20240704_082808 00_00_00-00_00_30.gif
对于接收Rx方向,我们本次DIY要实现一个Modbus-RTU从站协议。因此,帧尾的判定我们使用定时器超时的方式处理。定时器我们使用上一个实验中的Systick定时器的1ms计数器来实现。

总结

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

I2C外设阻塞式读取CW24C02

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

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

  1. int main(void)
  2. {
  3.   uint32_t i;
  4.   bsp_init();

  5.   InitTick(64000000);

  6.   log_printf("Hello 21ic, Hello CW32F030\r\n");
  7.   while (1)
  8.   {
  9.     if (time_line_ms == 0)
  10.     {
  11.       time_line_ms = 500;
  12.       PB08_TOG();
  13.       PB09_TOG();
  14.     }
  15.     if ((g_flag & 0x01) == 0x01)
  16.     {
  17.       g_flag &= ~(0x01);
  18.       I2C_MasterWriteEepromData(CW_I2C1, WRITEADDRESS, tx_buf, 8);
  19.       log_printf("EEPROM write:\r\n");
  20.       for (i = 0; i < 8; i++)
  21.       {
  22.         log_printf("%02X ", tx_buf[i]);
  23.       }
  24.       log_printf("\r\n");
  25.     }

  26.     if ((g_flag & 0x04) == 0x04)
  27.     {
  28.       g_flag &= ~(0x04);
  29.       I2C_MasterReadEepomData(CW_I2C1, WRITEADDRESS, rx_buf, 8);
  30.       log_printf("EEPROM read:\r\n");
  31.       for (i = 0; i < 8; i++)
  32.       {
  33.         log_printf("%02X ", rx_buf[i]);
  34.       }
  35.       log_printf("\r\n");
  36.     }
  37.   }
  38. }

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


总结

得益于武汉力源工作师开发、分享的固件库,我们在实现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的速率也较高。本次实验实现我们依然借助武汉力源的外设驱动库,源代码如下:

  1. int main(void)
  2. {
  3.   uint32_t i;
  4.   uint32_t flash_addr = 0;
  5.   bsp_init();

  6.   InitTick(64000000);

  7.   // DeviceID = SPI_FLASH_DeviceID();
  8.   // ManufactDeviceID = SPI_FLASH_ManufactDeviceID();
  9.   // JedecID = SPI_FLASH_JedecID();
  10.   // SPI_FLASH_UniqueID(UniqueID);
  11.   // log_printf("\r\nDeviceID = 0x%X\r\nManufactDeviceID = 0x%X\r\nJedecID = 0x%X", DeviceID, ManufactDeviceID, JedecID);
  12.   // log_printf("\r\nUniqueID = 0x");
  13.   // for (i = 0; i < 8; i++)
  14.   // {
  15.   //   log_printf("%X", UniqueID[i]);
  16.   // }

  17.   // if (JedecID == sJedecID)
  18.   // {
  19.   //   log_printf("\r\n\nFLASH Detected\r\n");
  20.   // }

  21.   SPI_FLASH_SectorErase(flash_addr);

  22.   for(i = 0; i < 64; i++)
  23.   {
  24.     tx_buf[i] = i;
  25.   }
  26.   SPI_FLASH_BufferWrite(tx_buf, flash_addr, 64);

  27.   SPI_FLASH_BufferRead(rx_buf, flash_addr, 64);

  28.   log_printf("flash read\r\n");
  29.   for (i = 0; i < 64; i++)
  30.   {
  31.     log_printf("%02X ", rx_buf[i]);
  32.   }
  33.   while (1)
  34.   {
  35.     if (time_line_ms == 0)
  36.     {
  37.       time_line_ms = 500;
  38.       PB08_TOG();
  39.       PB09_TOG();
  40.       // log_printf("hello world\r\n");
  41.     }
  42.   }
  43. }

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

总结

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

CRC-16硬件实现

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

  1. uint16_t CRC16_Calc_8bit(uint8_t CrcMode, uint8_t *pByteBuf, uint16_t ByteCnt)
  2. {

  3.     CW_CRC->CR = CrcMode;
  4.     while (ByteCnt)
  5.     {
  6.         CW_CRC->DR8 = *pByteBuf;
  7.         pByteBuf++;
  8.         ByteCnt--;
  9.     }
  10.     return (CW_CRC->RESULT16);
  11. }

总结

这个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的时延——系统肯定可以接受,这个功能极大改善了我的软件代码的质量。

  1. static const UCHAR aucCRCHi[] = {
  2.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  3.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  4.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  5.     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  6.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  7.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  8.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  9.     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  10.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  11.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  12.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  13.     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  14.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  15.     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  16.     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  17.     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  18.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  19.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  20.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  21.     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  22.     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  23.     0x00, 0xC1, 0x81, 0x40
  24. };

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

  49. USHORT
  50. usMBCRC16( UCHAR * pucFrame, USHORT usLen )
  51. {
  52.     UCHAR           ucCRCHi = 0xFF;
  53.     UCHAR           ucCRCLo = 0xFF;
  54.     int             iIndex;

  55.     while( usLen-- )
  56.     {
  57.         iIndex = ucCRCLo ^ *( pucFrame++ );
  58.         ucCRCLo = ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] );
  59.         ucCRCHi = aucCRCLo[iIndex];
  60.     }
  61.     return ( USHORT )( ucCRCHi << 8 | ucCRCLo );
  62. }

EEPROM中存储匹配参数

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

  1. typedef struct mb_filter_s
  2. {
  3.     uint8_t id_mask;
  4.     uint8_t func_id_mask;
  5.     uint8_t len_mask;
  6.     uint8_t err_mask;
  7. }mb_filter_t;

SPI Flash的日志数据存储

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

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

  1. typedef struct log_info_s
  2. {
  3.   uint8_t flag; // fixed to 0xA5;
  4.   uint8_t len;
  5.   uint8_t next;
  6.   uint8_t mode;
  7. }log_info_t;

视频展示
chenjun89 发表于 2024-7-9 08:27 来自手机 | 显示全部楼层
可以用开源的shell终端
AdaMaYun 发表于 2024-7-9 11:24 | 显示全部楼层
其实就是串口通讯,有相关协议的开发方式嘛
梅花香自123 发表于 2024-8-22 16:21 | 显示全部楼层
如何配置GPIO和USART外设。
 楼主| jobszheng 发表于 2024-8-22 20:25 | 显示全部楼层
梅花香自123 发表于 2024-8-22 16:21
如何配置GPIO和USART外设。

啥情况!?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

30

主题

740

帖子

23

粉丝
快速回复 在线客服 返回列表 返回顶部
认证:嵌入式技术专家
简介:热爱开源,乐于分享。在嵌入式技术领域里面,主攻通讯协议,Modbus,TCP/IP以及虚拟化和RTOS

30

主题

740

帖子

23

粉丝
快速回复 在线客服 返回列表 返回顶部