- 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;
视频展示