#申请原创# @21小跑堂
相遇 疫情期间,无意中在大大通平台上看到了大联大世平集团推出的基于灵动微MM32F0140 MCU制作的Arduino平台方案,在直播中了解到MM32F0410系列MCU功能特性,以及WPI详细介绍了开发的设计、功能和特点,对网友的很多提问也都进行了答复。活动结束后有幸获得了一块开发板,通过这几天的研究和把玩,今天来带大家一起玩转这块开发板,在程序设计的过程中有不少的注意点和优化设计分享给大家哦。有兴趣的小伙伴可以去观看直播回放哈:https://www.wpgdadatong.com/webinar/243499494
MM32F0140主要特点 - Arm Cortex-M0内核,运行频率高达72MHz
- 最高64KB FLASH和8KB SRAM
- 内置5个DMA通道
- 内置32-bit硬件除法单元
- 3组UART、2组SPI、2组I2C、1组I2C
- 1组FlexCAN,支持CAN2.0协议
- 1组高级定时器,可输出4通道带互补的PWM波形,支持死区和刹车功能
- 1组32-bit定时器、4组16-bit定时器
- 1组12-bit SAR ADC,最高支持1Msps采样率和13路采样通道
- 1组高速模拟比较单元
- 2.0V~5.5V宽工作电压
MM32F0140资源图
开发板功能示意图
开发板实物展示图
开发板功能介绍 大联大世平集团推出的基于灵动微MM32F0140系列MCU制作的Arduino平台方案开发板,搭配了众多的传感器件以及接口IC,例如ams TMF8821多点ToF、Novosense的NSHT35温湿传感器和NCA1042 CAN收发器、Vishay的TFDU4300红外收发器等等,另外还有onsemi的CAT24C03 EEPROM、Winbond的W25Q16JV SPI FLASH、LCD接口、按键、RGB LED等等……另外还兼容Arduino接口,方便更多的扩展与应用。通过这块开发板,我们可以实现如下功能: - 实现ADC采样
- 实现按键检测
- 实现FlexCAN通讯
- 实现EEPROM数据存取功能
- 实现DAC音频播放功能
- 实现IrDA红外通讯功能
- 实现LCD TFT液晶屏幕显示
- 实现NSHT35温湿检测功能
- 实现RGB LED控制功能
- 实现SWITCH开关状态检测功能
- 实现ToF高精度测距功能
- 实现SPI FLASH数据存取功能
- 实现Xmodem下载文件/数据到SPI FLASH的应用功能
需要说明的是在收到的开发上NSHT35温湿度传感器属于选焊器件,没有焊接在板子上;另外ToF高精度测距模块使用的TFM8821,对于它的操作需要原厂的技术支持并提供相应的参数文件;所以这两块功能作为保留,暂时无法进行操作和实现;而我们会将开发板上其余所有功能都一一实现,并进行演示和测试。
大大通上提供部分技术文档的下载:https://www.wpgdadatong.com/solution/detail?PID=5481,其中包含原理图、项目计划表、硬件BOM、以及硬件PCB图;但没有提供开发板的软件工程源代码,以及其它的一些手册资料也都没有开放出来,这让很多小伙伴在拿到这个开发板后,都只能看看原理图、看看开发板……要怎么去调试、演示成了最大的问题。下面我们一边分析硬件原理图,一边演示开发板功能,来实现上面提到的实现功能,分享给大家。
原理图设计
原理图的解读和建议 整个开发板由一个Micro USB接口供电,这个Micro USB仅为供电接口,不带有任何通讯调试功能哈;所以个人建议在下次改板时,USB接口可以加一个CH340,作为调试用,会很实用,也会很方便!
开发板上MM32F0140芯片的程序下载接口为1.27mm间距的2*5双排针,可能是为了节省PCB的空间吧,才使用了这们的接口;同样是通过SWD接口下载程序,我更喜欢4PIN(VCC、GND、SWDIO、SWDCLK)或者5PIN(VCC、GND、SWDIO、SWDCLK、NRST)这样的2.54间距排针,这样可以直接插上杜邦线就可以下载程序了,但现在板子上这样的接口,我还需要另外配一个2.54mm间距的2*10 接口转1.27mm间距的2*5接口的转接板,不是很方便。
整个硬件系统可以工作在5V电压下,也可以工作在3.3V电压下,可以通过JP1接口进行跳帽选择;但需要注意的是开发上的SPI FLASH和LCD都只能是3.3V供电的,所以在5V供电时,需要将这些元器件的连接断开。
官网上下载下来的原理图中关于RGB LED设计部分有错误,原理图中设计通过JP3、JP4、JP5这3个跳帽将RGB LED的3个控制引脚分为与PA2、PA1、PA3进行连接,但在实际收到的开发板上,这3个跳帽连接的是PB1、PA1、PB0这3个端口引脚,应该是为了避免端口功能重复使用,更新的原理图没有同步过来,这个在程序设计的时候,需要注意一下。
CAN通讯的收发芯片使用的是NCA1042,这个芯片的工作电压是5V,所以开发板在测试CAN功能时,一定要有5V电源输入;另外需要注意的是原理图中使用了PA8这个引脚来控制NCA1042的工作状态,需要将PA8置为低电平时,NCA1042才能够正常工作,如果PA8为高电平,那NCA1042则为待机状态,此时CAN收发器是不工作的,更没有数据传输,更有甚者在CAN上位机软件发送数据时,还会提示发送失败,这些需要注意。
在测试IrDA通讯时,需要通过JP8将TFDU4300的SD与GND相连接,这样IrDA才会处于正常工作的状态;另外就是TFDU4300收发数据是半双工的,即同一时刻只能发送数据或者接收数据,更达不到自发自收的功能,所以需要借助我们之前做的IrDA开发板来搭配测试。
开发平台环境 硬件包含:MM32F0140 Arduino开发板、ARM仿真器、USB转TTL调试工具、JTAG 20PIN转10PIN转换板、LCD TFT液晶显示屏、以及用于播放音乐用的音箱。 软件包含:Keil MDK集成开发环境、SecureCRT调试终端、Image2Lcd图片数据提取软件等。
RGB LED控制 使用GPIO的控制方式,循环让3个LED点亮,如果需要实现RGB呼吸灯效果,可以参考之前【MM32+模块】专题帖,实现代码和测试效果如下: void RGBLED_Toggle(void)
{
static uint8_t Step = 0;
switch(Step)
{
case 0:
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_RESET);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_SET);
break;
case 1:
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_SET);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_SET);
break;
case 2:
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_SET);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_RESET);
break;
default:
break;
}
Step = (Step + 1) % 3;
}
独立按键检测 通过时间片轮转调度的方式,软件延时去抖处理,实现了4个按键的独立检测,实现代码和测试效果如下: void BUTTON_SubScan(uint8_t *State, uint8_t *Count, uint8_t Value, char *Name)
{
if(*State == 0)
{
if(Value == Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 1;
printf("\r\n%s Pressed", Name);
}
}
else
{
if(Value != Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 0;
printf("\r\n%s Release", Name);
}
}
}
void BUTTON_Scan(void)
{
static uint8_t State[4] = {0, 0, 0, 0};
static uint8_t Count[4] = {0, 0, 0, 0};
BUTTON_SubScan(&State[0], &Count[0], GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0), "SW2");
BUTTON_SubScan(&State[1], &Count[1], GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_2), "SW3");
BUTTON_SubScan(&State[2], &Count[2], GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_3), "SW4");
BUTTON_SubScan(&State[3], &Count[3], GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2), "SW5");
}
SWITCH开关状态检测 SWITCH是一个4位的拨码开关,通过GPIO端口引脚的状态位来判断拨码开关所处的状态,实现代码和测试效果如下: void SWITCH_SubScan(uint8_t *State, uint8_t *Count, uint8_t Value, char *Name)
{
if(*State == 0)
{
if(Value != Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 1;
printf("\r\n%s -> OFF", Name);
}
}
else
{
if(Value == Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 0;
printf("\r\n%s -> ON ", Name);
}
}
}
void SWITCH_Scan(void)
{
static uint8_t State[4] = {0, 0, 0, 0};
static uint8_t Count[4] = {0, 0, 0, 0};
SWITCH_SubScan(&State[0], &Count[0], GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15), "SW1-1");
SWITCH_SubScan(&State[1], &Count[1], GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15), "SW1-2");
SWITCH_SubScan(&State[2], &Count[2], GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_14), "SW1-3");
SWITCH_SubScan(&State[3], &Count[3], GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13), "SW1-4");
}
ADC采样 ADC包含了实现掉电压检测功能和可调电位器的电压检测这两个功能,采用的单通道独立查询采样的实现方式,实现代码和测试效果如下: void ADC_Handler(void)
{
uint16_t Result = 0;
float Voltage = 0.0;
Result = ADC_Convert(ADC_Channel_10);
Voltage = (float)Result / 4096 * 3.3;
printf("\r\n");
printf("\r\nPB3 <-> ADC1_CH10 : %0.3fV", Voltage);
Result = ADC_Convert(ADC_Channel_12);
Voltage = (float)Result / 4096 * 3.3;
printf("\r\nPB7 <-> ADC1_CH12 : %0.3fV", Voltage);
printf("\r\n");
}
FlexCAN通讯 FlexCAN默认配置为500kbps的通讯速率,每间隔3秒中向CAN总线中发送一个报文;配置了一个接收邮箱,采用中断的方式,在接收到数据后通过调试终端软件打印显示出来,实现代码和测试效果如下: void FLEX_CAN_IRQHandler(void)
{
flexcan_frame_t flexcan_frame_rx;
if(0U != FLEXCAN_GetMbStatusFlags(FLEX_CAN1, 1 << RX_MESSAGE_BUFFER_NUM))
{
FLEXCAN_ClearMbStatusFlags(FLEX_CAN1, 1 << RX_MESSAGE_BUFFER_NUM);
FLEXCAN_ReadRxMb(FLEX_CAN1, RX_MESSAGE_BUFFER_NUM, &flexcan_frame_rx);
printf("\r\nReceived message from MB%d", RX_MESSAGE_BUFFER_NUM);
printf("\r\n");
printf("\r\nrx form = 0x%x", (flexcan_frame_rx.id & CAN_ID_STD_MASK) >> CAN_ID_STD_SHIFT);
printf("\r\nrx word0 = 0x%x", flexcan_frame_rx.dataWord0);
printf("\r\nrx word1 = 0x%x", flexcan_frame_rx.dataWord1);
printf("\r\n");
}
__DSB();
}
void CAN_Handler(void)
{
flexcan_frame_t flexcan_frame_tx;
flexcan_frame_tx.length = 0x08;
flexcan_frame_tx.type = (uint8_t)Enum_Flexcan_FrameTypeData;
flexcan_frame_tx.format = (uint8_t)Enum_Flexcan_FrameFormatStandard;
flexcan_frame_tx.id = FLEXCAN_ID_STD(0x123);
flexcan_frame_tx.dataWord0 = CAN_WORD0_DATA_BYTE_0(0x11) |
CAN_WORD0_DATA_BYTE_1(0x22) |
CAN_WORD0_DATA_BYTE_2(0x33) |
CAN_WORD0_DATA_BYTE_3(0x44);
flexcan_frame_tx.dataWord1 = CAN_WORD1_DATA_BYTE_4(0x55) |
CAN_WORD1_DATA_BYTE_5(0x66) |
CAN_WORD1_DATA_BYTE_6(0x77) |
CAN_WORD1_DATA_BYTE_7(0x88);
FLEXCAN_TransferSendBlocking(FLEX_CAN1, TX_MESSAGE_BUFFER_NUM, &flexcan_frame_tx);
}
IrDA红外通讯 在测试IrDA功能的时候需要搭配另外一块IrDA功能板进行测试,在上电的时候会自动向外发送一串数据,然后以中断方式接收数据并通过调试终端软件打印显示出来;实现代码和测试效果如下: void UART2_IRQHandler(void)
{
if(UART_GetITStatus(UART2, UART_IT_RXIEN) != RESET)
{
printf("%d ", UART_ReceiveData(UART2));
UART_ClearITPendingBit(UART2, UART_IT_RXIEN);
}
}
void IrDA_SendData(uint8_t Data)
{
UART_SendData(UART2, Data);
while(UART_GetFlagStatus(UART2, UART_IT_TXIEN) == RESET);
}
void IrDA_Test(void)
{
printf("\r\nIrDA TX : ");
for(uint8_t i = 0; i < 10; i++)
{
IrDA_SendData(i);
printf("%d ", i);
}
printf("\r\n");
}
EEPROM数据存取 测试函数向EEPROM中先写入100字节数据,再读取到缓存数组,通过调试终端软件打印显示出来;需要注意的是EEPROM是按PAGE进行写入,每个PAGE的写入操作需要一段时间的间隔,这个代码实现的时候需要特别注意;实现代码和测试效果如下: void EEPROM_Test(void)
{
uint8_t rBuffer[100], wBuffer[100];
uint8_t Address = 0, Length = 100;
printf("\r\n");
for(uint8_t i = 0; i < Length; i++)
{
rBuffer[i] = 0;
wBuffer[i] = i + 100;
}
printf("\r\nEEPROM Write......");
EEPROM_WriteData(Address, wBuffer, Length);
printf("\r\n");
printf("\r\nEEPROM Read :");
EEPROM_ReadData(Address, rBuffer, Length);
for(uint8_t i = 0; i < Length; i++)
{
if((i % 16) == 0) printf("\r\n");
printf("0x%02x ", rBuffer[i]);
}
printf("\r\n");
}
W25Q16数据存取 W25Q16用来保存WAV音频文件和字库文件,为了实现WAV播放音乐时流畅的效果,在读取数据时应尽可能的迅速,所以W25Q16的SPI接口使用DMA的通讯操作方式;在测试W25Q16时先对全片的所有SECTOR进行擦除操作,然后验证写数据和读数据的操作,实现代码和测试效果如下: void W25Q16_Test(void)
{
uint8_t Buffer[128];
W25Q16_ReadDeviceID();
W25Q16_ReadJEDEC_ID();
for(uint16_t i = 0; i < 512; i++)
{
printf("\r\nW25Q16 Erase Sector[%03d]......", i);
W25Q16_SectorErase(i);
}
printf("\r\n");
printf("\r\nW25Q16 Erase Sector[0]......");
W25Q16_SectorErase(0);
printf("\r\nW25Q16 Read :");
W25Q16_FastRead(0, Buffer, sizeof(Buffer));
for(uint8_t i = 0; i < sizeof(Buffer); i++)
{
if((i % 16) == 0) printf("\r\n");
printf("0x%02x ", Buffer[i]);
}
printf("\r\n");
printf("\r\nW25Q16 Write......");
memset(Buffer, 0, sizeof(Buffer));
for(uint8_t i = 0; i < sizeof(Buffer); i++)
{
Buffer[i] = i;
}
W25Q16_PageProgram(0, Buffer, sizeof(Buffer));
printf("\r\nW25Q16 Read :");
W25Q16_FastRead(0, Buffer, sizeof(Buffer));
for(uint8_t i = 0; i < sizeof(Buffer); i++)
{
if((i % 16) == 0) printf("\r\n");
printf("0x%02x ", Buffer[i]);
}
printf("\r\n");
}
Xmodem下载文件/数据 Xmodem功能通过UART1接口,使用SecureCRT软件将音频文件和字库文件下载到W25Q16存储芯片中;实现代码可以参考附件中的程序,也可以参考之前的Xmodem专题帖,测试效果如下:
LCD液晶显示 LCD的控制接口与W25Q16共用了SPI1的SCK和MOSI这两个引脚,但W25Q16需要发送数据和接收数据双向功能,但LCD只需要发送数据即可;所以为了保证W25Q16数据读写正常,在不操作W25Q16时需要将SPI1接收数据的功能关闭!!!LCD实现了图片显示和中英文字符显示,英文字符的字库存放在程序空间,而中文字库则存放在W25Q16存储芯片,实现代码和测试效果如下所示: void LCD_ShowLOG(uint8_t StartX, uint8_t StartY, const char *str)
{
while(*str != '\0')
{
if(*str < 0x7F)
{
if(StartX > (128 - 8))
{
StartX = 0; StartY += 16;
}
if(StartY > (128 - 16))
{
StartX = 0; StartY = 0;
LCD_ClearScreen(BACKCOLOR);
}
LCD_ShowEN(StartX, StartY, *str);
StartX += 0x08;
str += 0x01;
}
else
{
if(StartX > (128 - 16))
{
StartX = 0; StartY += 16;
}
if(StartY > (128 - 16))
{
StartX = 0; StartY = 0;
LCD_ClearScreen(BACKCOLOR);
}
LCD_ShowCN(StartX, StartY, str);
StartX += 0x10;
str += 0x02;
}
}
}
void LCD_DrawImage(void)
{
uint16_t Color = 0;
uint32_t Index = 0;
for(uint32_t i = 0; i < 47; i++)
{
for(uint32_t j = 0; j < 128; j++)
{
Color = gImage_**[Index++];
Color <<= 8;
Color |= gImage_**[Index++];
LCD_DrawPoint(j, i, Color);
}
}
}
WAV播放音乐 通过读取W25Q16存储芯片中的WAV音乐文件,进行解析,通过I2C接口将数据发送到HT5010功放芯片进行音乐播放;I2S使用了双缓存空间与DMA的数据传输方式,结合W25Q16存储芯片DMA读取数据的操作方式,加快数据读取速度,使音乐播放效果更流畅;实现代码和测试效果如下所示: void WAV_PlaySong(void)
{
WAV_TypeDef WaveFile;
if(WAV_PlayState == 0)
{
if(WAV_DecodeFile(&WaveFile, 0x00000000) == 0)
{
if((WaveFile.BitsPerSample == 16) && (WaveFile.nChannels == 2) &&
(WaveFile.SampleRate > 44000) && (WaveFile.SampleRate < 48100))
{
WAV_NextIndex = 0;
WAV_PlayEnded = 0;
WAV_TxLength = 0;
WAV_PlayState = 1;
printf("\r\n");
printf("\r\nWAV Data Size : %d, Data Start : 0x%08x", WAV_DataSize, WAV_Offset);
printf("\r\n");
WAV_PrepareData();
WAV_PlayHandler();
}
else
{
printf("\r\nWAV File Format Error!\r\n"); return;
}
}
else
{
printf("\r\nNo WAV File!\r\n"); return;
}
}
else
{
printf("\r\nThe Song Is Not Over Yet!\r\n");
}
}
附件 软件工程源代码:
硬件设计原理图:
硬件BOM表、硬件PCB文档:
器件手册:
资源文件:
|
详尽介绍了开发板硬件资源,同时利用自己的知识突破官方没有demo的限制。打通开发板试用限制。同时提供丰富的参考完档,便于玩转开发板。 所有的硬件资料均为厂商提供,代码WPI未提供,但是在灵动官网是极易获取的。鉴于文章结构完整,内容丰富,给予100元奖励