在上一期推送中,我们简单评测了三款RISC-V MCU产品《国产自研RISC-V MCU哪家强?我们让工程师评测了几款》。
今天,我们选择三款产品,从开箱、电灯、测试、仿真多维度,深度评测国产自研 RISC-V MCU的能力。
1. 先楫HPM6750
作者:xiashuang
原帖地址:lhttp://www.eeworld.com.cn/a10ibTC
初次上手
去年开始了解到先辑半导体,一直关注着这家公司,后来在淘宝上看到野火有卖HPM6750和HPM6450芯片,想等着野火出开发板时买一块试试(先辑官网的开发板一直都显示不能订购)。上次EEWORLD上直播正好通过直播申请了,很幸运拿到了HPM6750evkmini开发板一块,接下来我将分享一些我的学习经验。
收到板子后,先把这个评测汇总帖子挨个看了下http://www.eeworld.com.cn/aTu14aT,在结合SDK下的文档基本了解了芯片硬件和IDE,整体来说上手是比较容易的,基本的操作我也不再赘述,只是从使用上说说比较绕弯路的几个点吧。
1.从官网下载的SEGGER Embedded Studio for RISC-V安装后Option对应设置,个人习惯把缩进和Tab都改为4,不然移植以前的文件格式很难看。
2.我以前使用KEIL时大量注释编码和汉字显示使用了GB2312,在SES中可以做如下设置,使显示正确。
3.如果使用SES内的SDK包生成工程文件如果调试器使用FTDI需要设置GDB调试,同时配置openocd比较麻烦,建议使用SDK命令行配置(generate_project -b hpm6750evkmini)。
4.关于分散加载文件可以使用generate_project -b hpm6750evkmini 后增加-t选项例如: -t flash_sdram_xip 也可以在Linker里面选择,选择完后重新编译即可。
分散加载文件官方SDK已经写好,因为HPM6750的内部SRAM均固定大小所以基本不需要修改配置,我主要使用两种类型的:
①flash_xip 代码存储在flash中,上电后从flash取指令和数据分别进入ILM和DLM,当缓存未命中会影响执行速度,好处不用外扩SDRAM,仅仅使用低成本flash就可以,关键代码和中断也可放到SRAM提高速度。
②flash_sdram_xip 代码存储在flash中,上电后先把flash数据复制到SDRAM中,程序在SDRAM中执行,好处是程序执行速度快,但SDRAM较昂贵,另外不同SDRAM上电初始化代码不太一样,需要重写代码。另外官方还提供flash_uf2和flash_sdram_uf2大概是usb app方式。
Modbus是常用的工业通讯协议,本次在HPM上移植Modbus RTU,主要是熟悉uart和定时器。本来想移植freemodbus,但是freemodbus只支持单从机,前段时间在RT-THREAD社区里看到agile_modbus,看了下例程发现和libmodbus一样比较简单,并且支持多主机多从机,正好想试一下HPM6750的uart和定时器,于是使用uart接收和发送中断以及定时器产生超时中断实现modbus rtu。
裸机移植agile_modbus
原帖地址:http://www.eeworld.com.cn/aDCK0u5
移植步骤如下:1.从GitHub上下载agile_modbus文件,在工程中加入相关源文件。
2.增加头文件路径
3.协议栈初始化
/*init modbus stack*/
uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
agile_modbus_rtu_t ctx_rtu;
agile_modbus_t *ctx = &ctx_rtu._ctx;
agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
agile_modbus_set_slave(ctx, 1);
4.串口使用uart13,中断代码如下
void uart_isr(void)
{
uint8_t c;
uint8_t uart_irq_state = uart_get_irq_id(TEST_UART1);
if (uart_irq_state & uart_intr_id_rx_data_avail)
{
if (status_success != uart_receive_byte(TEST_UART1, &c)) {
while (1) {
}
}
//定时器重置计数
gptmr_channel_reset_count(MODBUS_CALLBACK_TIMER, MODBUS_CALLBACK_TIMER_CH);
//printf("R");
//启动定时器
if(buff_index == 0)
{
gptmr_start_counter(MODBUS_CALLBACK_TIMER, MODBUS_CALLBACK_TIMER_CH);
uart_rx_outtime = 0;//接收超时状态为0
//printf("S");
}
data_buff[buff_index] = c;
buff_index++;
//接收数据过多,重新接收
if (buff_index >= 260) {
//uart_disable_irq(TEST_UART1, uart_intr_rx_data_avail_or_timeout);
//uart_enable_irq(TEST_UART1, uart_intr_tx_slot_avail);
buff_index = 0;
}
}
if (uart_irq_state & uart_intr_id_tx_slot_avail)
{
if (status_success != uart_send_byte(TEST_UART1, data_buff[data_count])) {
while (1) {
}
}
data_count ++;
if(data_count >= buff_index){
buff_index = 0;
data_count = 0;
uart_rx_outtime = 0;
uart_flush(TEST_UART1);
uart_disable_irq(TEST_UART1, uart_intr_tx_slot_avail);
uart_enable_irq(TEST_UART1, uart_intr_rx_data_avail_or_timeout);
}
}
}
SDK_DECLARE_EXT_ISR_M(TEST_UART_IRQ1, uart_isr)
5.定时器配置
void modbus_timer_create(uint32_t ms, modbus_timer_cb cb)
{
uint32_t gptmr_freq;
gptmr_channel_config_t config;
gptmr_channel_get_default_config(MODBUS_CALLBACK_TIMER, &config);
clock_add_to_group(MODBUS_CALLBACK_TIMER_CLK_NAME, 0);
gptmr_freq = clock_get_frequency(MODBUS_CALLBACK_TIMER_CLK_NAME);
config.reload = gptmr_freq / 1000 * ms;
gptmr_channel_config(MODBUS_CALLBACK_TIMER, MODBUS_CALLBACK_TIMER_CH, &config, false);
gptmr_enable_irq(MODBUS_CALLBACK_TIMER, GPTMR_CH_RLD_IRQ_MASK(MODBUS_CALLBACK_TIMER_CH));
intc_m_enable_irq_with_priority(MODBUS_CALLBACK_TIMER_IRQ, 1);
//gptmr_start_counter(MODBUS_CALLBACK_TIMER, MODBUS_CALLBACK_TIMER_CH);
}
6.收到UART第一个接收中断启动定时器,以后每个中断定时器重新计数,超过T3.5后定时器超时更新标志,进行modbus协议栈处理,如果需要发送数据则发送数据
if(uart_rx_outtime == 1)
{
//printf("t");
uart_rx_outtime = 0;
memcpy(ctx_read_buf,data_buff,buff_index);
int send_len = agile_modbus_slave_handle(ctx, buff_index, 0, slave_callback, NULL);
if (send_len > 0)
{
data_count = 0;
buff_index = send_len;
memcpy(data_buff,ctx_send_buf,buff_index);
uart_flush(TEST_UART1);
uart_enable_irq(TEST_UART1, uart_intr_tx_slot_avail);
uart_disable_irq(TEST_UART1, uart_intr_rx_data_avail_or_timeout);
}
else{
buff_index = 0;
}
}
7.slave_callback不需要做修改,因为我使用逻辑,所以注释掉互斥访问
总结:HPM官方提供UART中断收发代码中中断状态读取在实际使用中不太合理,发送使用阻塞发送若果不使用阻塞发送将会导致程序卡死在接收中断处理里面的while(1),增加中间变量uint8_t uart_irq_state = uart_get_irq_id(TEST_UART1) 使用中间变量判断状态就OK了,怀疑每次读中断状态将把中断寄存器中断清除。agile_modbus比freemodbus移植和使用点单多了,如果有需要使用modbus的建议试试看看。
软件模拟SPI采集ADS1263(32位ADC)数据
原帖地址:http://www.eeworld.com.cn/aD8erzT
前段时间在某宝上买了几片TI的32位ADC:ADS1263,官方手册上的性能很强,最高采样到38.4k(26us)需要的处理器性能必须要具有很强的运算功能,正好试下HPM6750,于是画了一块PCB板,经过一周的等待终于到了,焊接了必要的元件就开干。
为了先验证板子的情况,先用软件模拟SPI进行实验,需要7根信号线进行连接通讯,定义的GPIO如下:
*RST---------PE25
*DRDY--------PE26
*MISO--------SPI2.MISO PB25
*MOSI--------SPI2.MOSI PB22
*SCK---------SPI2.SCK PB21
*CS----------PF1
*START-------PF4
先初始化IO口,时钟的开启已经在board_init()中完成,所以指示配置一下寄存器就行
/*设置IO口为通用IO*/
HPM_IOC->PAD[IOC_PAD_PE25].FUNC_CTL = IOC_PE25_FUNC_CTL_GPIO_E_25;
HPM_IOC->PAD[IOC_PAD_PE26].FUNC_CTL = IOC_PE26_FUNC_CTL_GPIO_E_26;
HPM_IOC->PAD[IOC_PAD_PB25].FUNC_CTL = IOC_PB25_FUNC_CTL_GPIO_B_25;
HPM_IOC->PAD[IOC_PAD_PB22].FUNC_CTL = IOC_PB22_FUNC_CTL_GPIO_B_22;
HPM_IOC->PAD[IOC_PAD_PB21].FUNC_CTL = IOC_PB21_FUNC_CTL_GPIO_B_21;
HPM_IOC->PAD[IOC_PAD_PF01].FUNC_CTL = IOC_PF01_FUNC_CTL_GPIO_F_01;
HPM_IOC->PAD[IOC_PAD_PF04].FUNC_CTL = IOC_PF04_FUNC_CTL_GPIO_F_04;
/*配置输入输出*/
/*ps-上下拉电阻 PE-上下拉开关 SMT-施密特 DS-驱动强度 OD-开漏 MS-电压选择*/
uint32_t pad_ctl_out = IOC_PAD_PAD_CTL_PE_SET(1) | IOC_PAD_PAD_CTL_PS_SET(1);
uint32_t pad_ctl_in = IOC_PAD_PAD_CTL_PE_SET(1) | IOC_PAD_PAD_CTL_PS_SET(1)|IOC_PAD_PAD_CTL_SMT_SET(1);
gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_DO_GPIOE, 25, 1);
gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_DO_GPIOB, 22, 1);
gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_DO_GPIOB, 21, 1);
gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_DO_GPIOF, 01, 1);
gpio_set_pin_output_with_initial(HPM_GPIO0, GPIO_DO_GPIOF, 04, 1);
HPM_IOC->PAD[IOC_PAD_PE25].PAD_CTL = pad_ctl_out;
HPM_IOC->PAD[IOC_PAD_PB22].PAD_CTL = pad_ctl_out;
HPM_IOC->PAD[IOC_PAD_PB21].PAD_CTL = pad_ctl_out;
HPM_IOC->PAD[IOC_PAD_PF01].PAD_CTL = pad_ctl_out;
HPM_IOC->PAD[IOC_PAD_PF04].PAD_CTL = pad_ctl_out;
HPM_IOC->PAD[IOC_PAD_PE26].PAD_CTL = pad_ctl_in;
HPM_IOC->PAD[IOC_PAD_PB25].PAD_CTL = pad_ctl_in;
相应的IO输出及输入读取如下:
#define ADS1263_RESET_H gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOE, 25, 1)
#define ADS1263_RESET_L gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOE, 25, 0)
#define ADS1263_START_H gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOF, 4, 1)
#define ADS1263_START_L gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOF, 4, 0)
#define ADS1263_CS_H gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOF, 1, 1)
#define ADS1263_CS_L gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOF, 1, 0)
#define ADS1263_SCLK_H gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOB, 21, 1)
#define ADS1263_SCLK_L gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOB, 21, 0)
#define ADS1263_DIN_H gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOB, 22, 1)
#define ADS1263_DIN_L gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOB, 22, 0)
#define ADS1263_DOUT gpio_read_pin(HPM_GPIO0, GPIO_DI_GPIOB, 25)
#define ADS1263_DRDY gpio_read_pin(HPM_GPIO0, GPIO_DI_GPIOE, 26)
初始化ADS1263
int init_ads1263(void)
{
printf("*************************************************************\r\n");
printf("* *\r\n");
printf("* ADS1263 TEST ^_^ *\r\n");
printf("* *\r\n");
printf("*************************************************************\r\n");
GPIO_Configuration();
ADS1263_INIT();//ADS1263初始化
ADS1263_CS_L;
Delay(0xf);
ADS1263_WRITE(0x08);//START1 command,当START引脚为低电平时,可由此命令启动ADC1的转换。
ADS1263_CS_H;
Delay(0xf);
return 0;
}
读取AD值
int read_ads1263(void)
{
if(ADS1263_DRDY != 1)
{
ADS1263_CS_L;
Delay(0xf);
ADS1263_WRITE(0x12);//读取ADC1
STATUS=ADS1263_READ_REG();
ADC1_DATA=ADS1263_READ();
checksum = ADS1263_READ_REG();
ADS1263_CS_H;
count++;
D[47]++;
if(D[76] > 0)
{
ADC1_DATA = Filter_ch1(ADC1_DATA,D[76],D[77]);
}
ADC1_DATA = CELL_ADSOURSE_FILTER(ADC1_DATA,D[78],D[79]);
mv_Now = ADC1_DATA / 2147483648.0 * 2500 / 32;//mv数
//测试重量参数
Weight = (mv_Now - mv_Zero)/ 10.0f * mv_Full * mv_Cali;
INT32toREG(ADC1_DATA,&D[0]);//源码
FP32toREG(mv_Now,&D[2]);//mv数
FP32toREG(Weight,&D[4]);//重量
//校秤参数
mv_Zero = REGtoFP32(&D[70]);//mv零点
mv_Full = REGtoFP32(&D[72]);//满量程
mv_Cali = REGtoFP32(&D[74]);//校准系数
return ADC1_DATA;
}
else
return 0;
}
利用上次移植的modbus 裸机例子,在主循环中不断查询AD状态读取。连接好线,接上称重传感器开始测试。
MODBUS上位机画面,使用50kg C3电阻应变桥式称重传感器采样400次精度在±1g,后来降低采样到60次/s,滑动平均5次,精度在±0.2g。
逻辑分析仪抓取波形图。采用软件模拟SPI,速度在3M左右,读取命令+状态+4字节数据+校验共7个字节数据在20.5us左右。
实验总结:
1.HPM6750在配置IO时要注意名称,因为IO引脚较多,宏定义也比较多,在初始化容易写错(这次因为IO编号写错导致两个IO口没有输出,查了1个多小时)。看来图形化代码工具还是很有必要的。
2.HPM6750的驱动强度和施密特单独出来和I.MX RT系列比较像增加了IO控制的灵活性,PCB上高速信号的抗信号反射电阻和驱动限流电阻都可以省去了,等以后试下芯片的施密特能否代替外部输入上的74HC14。
3.HPM6750运算速度很快,等以后试试高阶FIR看看
4.ADS1263的高速采样及其以来模拟电源的纹波,对内部DCDC电源还是要增加滤波器,采样60HZ对50-hz和60hz纹波抑制后精度一下就上来了。
测试CAN收发
原帖地址:http://www.eeworld.com.cn/ajzHiHO
HPM6750有4个CAN控制器,支持CANFD,目前在工业上用的还是CAN2.0比较多,本次测评使用CAN1控制器。CAN驱动芯片使用TI的VD230,电脑端使用了一个USB转CAN调试器,带有调试上位机软件。
连线如下:
1.初始化CAN引脚和时钟
HPM_IOC->PAD[IOC_PAD_PE31].FUNC_CTL = IOC_PE31_FUNC_CTL_CAN1_TXD;
HPM_IOC->PAD[IOC_PAD_PE30].FUNC_CTL = IOC_PE30_FUNC_CTL_CAN1_RXD;
/* Set the CAN1 peripheral clock to 80MHz */
clock_set_source_divider(clock_can1, clk_src_pll1_clk1, 5);
freq = clock_get_frequency(clock_can1);
2.初始化CAN
can_config_t can_config;
can_get_default_config(&can_config);
can_config.baudrate = 500000; /* 500kbps */
can_config.mode = can_mode_normal;
board_init_can(HPM_CAN1);
uint32_t can_src_clk_freq = board_init_can_clock(BOARD_APP_CAN_BASE);
hpm_stat_t status = can_init(HPM_CAN1, &can_config, can_src_clk_freq);
if (status != status_success) {
printf("CAN initialization failed, error code: %d\n", status);
return;
}
3.设置CAN中断
can_enable_tx_rx_irq(HPM_CAN1, CAN_EVENT_RECEIVE);
intc_m_enable_irq_with_priority(IRQn_CAN1, 1);
4.CAN中断
SDK_DECLARE_EXT_ISR_M(IRQn_CAN1, test_can1_isr);
void test_can1_isr(void)
{
uint8_t flags = can_get_tx_rx_flags(HPM_CAN1);
if ((flags & CAN_EVENT_RECEIVE) != 0) {
can_read_received_message(HPM_CAN1, (can_receive_buf_t *)&s_can_rx_buf);
has_new_rcv_msg = true;
}
if ((flags & (CAN_EVENT_TX_PRIMARY_BUF | CAN_EVENT_TX_SECONDARY_BUF))) {
has_sent_out = true;
}
if ((flags & CAN_EVENT_ERROR) != 0) {
has_error = true;
}
can_clear_tx_rx_flags(HPM_CAN1, flags);
error_flags = can_get_error_interrupt_flags(HPM_CAN1);
can_clear_error_interrupt_flags(HPM_CAN1, error_flags);
}
5.CAN发送接收到的数据
while (!has_new_rcv_msg) {
}
has_new_rcv_msg = false;
show_received_can_message((const can_receive_buf_t *)&s_can_rx_buf);
can_transmit_buf_t tx_buf;
memset(&tx_buf, 0, sizeof(tx_buf));
tx_buf.dlc = s_can_rx_buf.dlc;
tx_buf.id = 0x321;
uint32_t msg_len = can_get_data_bytes_from_dlc(s_can_rx_buf.dlc);
memcpy(&tx_buf.data, (uint8_t *)&s_can_rx_buf.data, msg_len);
status = can_send_message_blocking(BOARD_APP_CAN_BASE, &tx_buf);
if (status != status_success) {
printf("CAN sent message failed, error_code:%d\n", status);
return;
}
6.测试程序,设置CAN调试器为CA2.0,波特率500k,标准模式,依次发送数据,接收均正常。
HPM6750有4个CAN控制器,支持CANFD,目前在工业上用的还是CAN2.0比较多,本次测评使用CAN1控制器。CAN驱动芯片使用TI的VD230,电脑端使用了一个USB转CAN调试器,带有调试上位机软件。
测试以太网控制器 ENET
原帖地址:http://www.eeworld.com.cn/aeHOOKO
HPM6750有2 个千兆以太网控制器,MINI板引出了一个RMII接口以太网控制器HPM_ENET1的PIN,看SDK中有一个lwip的tcpecho例程默认使用的PHY是DP83848,翻了翻箱底找出了个DP83848模块开干。
1.连接杜邦线
2.修改例程中netconf.h的IP如下:
/* Static IP ADDRESS */
#define IP_ADDR0 192
#define IP_ADDR1 168
#define IP_ADDR2 1
#define IP_ADDR3 108
/* NETMASK */
#define NETMASK_ADDR0 255
#define NETMASK_ADDR1 255
#define NETMASK_ADDR2 255
#define NETMASK_ADDR3 0
/* Gateway Address*/
#define GW_ADDR0 192
#define GW_ADDR1 168
#define GW_ADDR2 1
#define GW_ADDR3 1
3.修改tcp_echo.h中TCP端口号:
#define TCP_ECHO_PORT (502U)
4.电脑修改网口IP
5.编译下载程序后打开TCP后TCP连续发送后echo回显。
这个例程折腾了很久,最后用手按着杜邦线才收发成功,这种高速的接口用杜邦线连接就是个考验,接口连接不牢固以及长线的阻抗匹配等都是重要因素,本来想移植Modbus TCP试试,但硬件不允许等以后有机会了再弄吧!
测试I2C与ADS1115通信
原帖地址:http://www.eeworld.com.cn/aLeHenD
HPM6750有4个I2C,HPM6750EVKMINI排针引出了2个I2C,模块侧有上拉电阻,所以使用板子上的I2C0,但比较遗憾的是HPM6750的I2C最高速度只支持1000k,不知道以后会不会支持3.4M。
ADS1115是TI家的兼容 I 2C 的 16 位高精度低功耗模数转换器 (ADC),I2C的最高速度是3.4M,本来想测一下3.4M的I2C,后来查看HPM6750的手册看不支持3.4M。
找了个ADS1115模块,连接杜邦线开干。
主要程序如下:
1.初始化I2C,速度为1000K
init_i2c_pins(TEST_I2C);
i2c_config_t configi;
uint32_t freq;
configi.i2c_mode = i2c_mode_fast_plus ;//i2c_mode_normal
configi.is_10bit_addressing = false;
freq = clock_get_frequency(TEST_I2C_CLOCK_NAME);
stat = i2c_init_master(TEST_I2C, freq, &configi);
if (stat != status_success) {
return stat;
}
2.定义I2C地址,因为我接线将ADS1115的ADDR直接接地,所以I2C地址为0x48
#define ADS1115_WRITE_ADDRESS (0x90>>1)//0x90
#define ADS1115_READ_ADDRESS (0x90>>1)//0x91
3.配置寄存器
void ads1115_config_register(uint8_t pointADD,uint8_t configH,uint8_t configL)
{
uint8_t reg_data[3]={pointADD,configH,configL};
if (status_success != i2c_master_write(TEST_I2C, ADS1115_WRITE_ADDRESS, reg_data, 3)) {
printf("IIC write failed");
printf("ads1115 Config Register error!!!\r\n");
while (1) {
}
}
}
4.读数据
int16_t ads1115_read_data(void)
{
int16_t data;
uint8_t rx_data[2]={0};
if (status_success != i2c_master_write(TEST_I2C, ADS1115_WRITE_ADDRESS, 0x00, 1)) {
printf("IIC write failed");
printf("ads1115 convert Register error!!!\r\n");
while (1) {
}
}
if (status_success != i2c_master_read(TEST_I2C, ADS1115_READ_ADDRESS, rx_data, 2)) {
printf("IIC read failed\n");
printf("ads1115 read data error!!!\r\n");
while (1) {
}
}
data=rx_data[0]<<8 | rx_data[1];
return data;
}
5.获取电压值
double ads1115_get_voltage_val(uint8_t pointADD,uint8_t configH,uint8_t configL)
{
double val;
int16_t ad_val;
ads1115_config_register(pointADD,configH,configL);
delay_ms(10);//等待10ms
ad_val=ads1115_read_data();
if((ad_val==0x7FFF)|(ad_val==0X8000))//是否超量程了
{
ad_val=0;
printf("over PGA\r\n");
}
switch((0x0E&configH)>>1)//量程对应的分辨率
{
case(0x00):
val=(double)ad_val*187.5/1000000.0;//
break;
case(0x01):
val=(double)ad_val*125/1000000.0;
break;
case(0x02):
val=(double)ad_val*62.5/1000000.0;
break;
case(0x03):
val=(double)ad_val*31.25/1000000.0;
break;
case(0x04):
val=(double)ad_val*15.625/1000000.0;
break;
case(0x05):
val=(double)ad_val*7.8125/1000000.0;
break;
}
return val;
}
6.下载程序分别测试0V和3.3V
总结:1.HPM6750的I2C最高速率目前是1000K,2.HPM6750的库中I2C是给7bit地址,读写函数自动移位和增加读写位,所以只需要定义I2C从机实际地址就可以。
16位ADC测试
原帖地址:http://www.eeworld.com.cn/aqrDSyD
HPM6750含有1 个 16 位 ADC,2MSPS 采样率,看起来挺诱人的,连线试试看。
使用官方SDK中ADC16例程,翻出来古老的51开发板,使用里面的电位器模拟量,连线如下:
我想直接算出电压比较,在采样结果程序中增加一行。
printf("value is %f",result* 3.3 / 65535);
编译下载输出如下:
看起来偏差很大,于是乎看手册想把采样速率或者采样平均数增加,但手册写的很简单,寄存器的解释看起来也很仓促,那就盲改程序吧:
把配置函数void init_common_config(adc16_conversion_mode_t conv_mode)中ch_cfg.sample_cycle修改如下:
ch_cfg.sample_cycle = 0x1ff;//20 0x1ff
继续编译下载查看输出:
看起来好多了,再增加一点滑动平均滤波
#define num 20
int32_t value1 = 0;
for(int i = num; i >= 0; i--)
{
data[i+1] = data;
value1 += data;
}
data[0] = result;
value1 += result;
value1 /= (num + 1);
printf("value is %f",value1 * 3.3 / 65535);
编译下载输出如下:
旋转电位器测试了下响应很快。
总结:
1.本次只是简单测试了下ADC,使用的是电位器分压,没有模拟前端缓冲和滤波,会影响精度和稳定;
2.使用的参考电压均为电脑USB供电,纹波会比较大也会影响精度,但通过滤波还是能很好的反应芯片本身的模拟性能;
3.吐槽一下手册确实太精简了,希望再详尽一点。
2. 博流BL606P音视频开发板
作者:qinyunti
CDK仿真体验
原帖地址:http://www.eeworld.com.cn/a04ezf1
仿真在嵌入式开发中非常重要,一款芯片和IDE的仿真能力及其使用友好性上非常重要。
很多问题必须要仿真才能高效解决。仿真的断点,变量查看,寄存器查看,栈回溯,条件断点,数据断点,存储查看等等都非常重要。
首先我们就体验下CDK的仿真环境。
点击红色的d下载并进入仿真环境:
如果代码没有修改可以直接点击蓝色d进入仿真环境。
调试窗口:菜单栏Debug->Debug Views可以显示各窗口。
代码断点:c代码断点,在代码前点击右键->Toggle Breakpoints,打代码断点,然后点击运行即可运行到断点处。
汇编级断点:有时c代码断点并不精确,需要定位到具体那一条指令就打开汇编窗口在汇编代码前单击打断点。
单步运行:如下三个按钮分别是单步进入函数,单步不进入函数,跳出函数。
内核寄存器:内核寄存器在判断处理器运行状态异常原因时很重要。
C和汇编对照:C有时并不能看到具体的行为,只有和汇编对照才知道到底执行的是什么指令,在分析编译器优化到这问题时就需要一条条看汇编代码。
函数内局部变量:自动显示函数内局部变量还是比较方便的。
函数调用栈:可以知道函数的调用路径,分析在什么执行路径下出现问题。
内存查看:在协议分析,数据处理等时往往需要查看缓冲区的内容导致是什么,或者查看寄存器的只。
Watch全局变量:选中全局变量右键点击 add watch ‘xxx’,就可以实时查看变量的值。
总结:总的来说基本的仿真功能都有,但是没有外设寄存器查看,内核外设查看等。仿真操作也不是很顺畅经常卡死,用户体验有待增强。
RGB点灯
原帖地址:http://www.eeworld.com.cn/anPavLS
拿到开发板之后马上点个灯是对开发板最起码的尊重,所以我们这一篇就来点板载的RGB灯。
下面开始创建工程。菜单栏 View -> Show Welcome Page,新建工程。
搜索BL606创建工程。
输入工程名LED,下载方案。
指定工程名和路径。
左侧工程红色的表示没有下载成功。
右键点击从Web下载。
修改脚本:LED\__workspace_pack__\bl606p_evb_e907\v7.6.2\script\aft_build.sh的如下位置加-kp参数,否则重启后不能运行。
修改源码:
LED\app\app_main.c中
#include
#include
main函数中
csi_pin_set_mux(GPIO_PIN_17, PIN_FUNC_GPIO);
csi_pin_set_mux(GPIO_PIN_18, PIN_FUNC_GPIO);
csi_pin_set_mux(GPIO_PIN_19, PIN_FUNC_GPIO);
gpio_dev_t gpio1 = { 0, OUTPUT_PUSH_PULL, NULL };
gpio_dev_t gpio2 = { 0, OUTPUT_PUSH_PULL, NULL };
gpio_dev_t gpio3 = { 0, OUTPUT_PUSH_PULL, NULL };
gpio1.port = GPIO_PIN_17;
gpio2.port = GPIO_PIN_18;
gpio3.port = GPIO_PIN_19;
hal_gpio_init(&gpio1);
hal_gpio_init(&gpio2);
hal_gpio_init(&gpio3);
while(1)
{
aos_msleep(1000);
hal_gpio_output_high(&gpio1);
hal_gpio_output_low(&gpio2);
hal_gpio_output_low(&gpio3);
aos_msleep(1000);
hal_gpio_output_low(&gpio1);
hal_gpio_output_high(&gpio2);
hal_gpio_output_low(&gpio3);
aos_msleep(1000);
hal_gpio_output_low(&gpio1);
hal_gpio_output_low(&gpio2);
hal_gpio_output_high(&gpio3);
LOGD("INFO", "Hello World\r\n");
}
编译:
下载:将如此说四个跳线帽跳线到JTAG这一边(靠近RGBLED这一边)。
点击下载:
运行:将上述跳线帽JP678跳到另外一边,上电可以看到RGB灯三色依次亮灭。
总结
RGB三色LED和JTAG引脚复用,如果用到RGB三色LED的化要要反复跳线,下载时跳线到JTAG,运行时跳线到LED, 对于需要频繁跳线的使用拨码开关更好,跳线跳来跳去跳线帽都不知道掉到哪去了,并且频繁插拔都会直接接触IC的引脚,又没有做静电保护设计,引脚很容易静电损坏。做的东西产品思维需要提高。
需要修改脚本aft_build.sh添加-kp参数再下载才能运行, 建议提供给用户的Demo一定要是开箱即用的,还需要改这么偏僻的地方,对于新使用一个平台来说早就劝退了,比较耽误用户的时间,用户体验有待提高。
居然没有复位按键,复位都需要重新插拔USB,体验不太好。
RGB点灯之后JTAG不能下载问题解决
原帖地址:http://www.eeworld.com.cn/aeX1qLC
下载RGB点灯程序之后发现无法JTAG下载了。
由于RGB的GPIO17 18 19和JTAG复用了,RGB点灯之后,用户代码将引脚配置为了IO,所以就用不了JTAG了。所以需要要停止用户代码运行,然后连接JTAG。
可以断开USB电源,按住BOOT按键,然后接上USB电源上电,此时不会运行用户程序,就可以JTAG下载了,下载之后就可以松开BOOT按键了。
ADC测试-虚拟示波器Demo
原帖地址:http://www.eeworld.com.cn/aHG8uf5
我们测试了IO,继续ADC接口测试。我们可以采集ADC值从串口打印,然后PC端接收打印的的值进行解析,曲线显示,这样就实现了虚拟示波器的功能。
引脚:使用GPIO16对应CH8GPIO16_ADC_CH8。
代码:
包含头文件
#include
#include
设置引脚
csi_pin_set_mux(GPIO_PIN_16, GPIO16_ADC_CH8);
配置ADC
adc_dev_t adc;
adc.port = 8;
adc.config.sampling_cycle = 100;
hal_adc_init(&adc);
采集值并打印
while(1)
{
uint32_t value = 0;
hal_adc_value_get(&adc, &value, HAL_WAIT_FOREVER);
LOGD("INFO", "/* %d */\r\n",value);
}
测试:使用旋转变压器分压,输入模拟电压,到引脚GPIO16。
https://serial-studio.github.io/下下载安装Serial Studio
点击JSON编辑器按如下设置:
点击添加组别:
添加数据集:
按如下设置点击申请:
连接串口控制台可以看到打印:
仪表盘可以看到曲线:
以上完成了ADC的测试,实现了虚拟示波器的功能。说实话官方的文档写的太随意了,参数都不知道怎么写,很多时候靠猜,Demo也不完整,希望文档方面能加强。另外官方的SDK也是一言难尽,文档又不规范,所以只能说测试一下还可以,要用到产品还有很长的路要走,至少文档这是一个态度问题,无关技术。好用的东西才会有人用,所以文档用户体验很重要。
对比https://www.eembc.org/coremark/scores.php。以上结果仅作参考,因为和配置关系很大,比如编译器优化,主频,cache的使能等等。
PWM测试-呼吸灯效果实现
原帖地址:http://www.eeworld.com.cn/amjjXb5
继续接口测试,测试PWM的输出,测试其频率和占空比的设置,以及使用PWM控制LED实现呼吸灯的效果。
引脚:使用GPIO16:
对应PWM0的输出GPIO16_PWM0_CH0P
代码:
包含头文件
#include
引脚配置
csi_pin_set_mux(GPIO_PIN_16, GPIO16_PWM0_CH0P);
PWM配置
pwm_dev_t pwm;
pwm.port = 0;
pwm.config.freq = 5000;
pwm.config.duty_cycle = 0.2f;
hal_pwm_init(&pwm);
启动
hal_pwm_start(&pwm);
测试
输出不同频率不同占空比的值,使用逻辑分析仪测试。
pwm.config.freq = 1000;
pwm.config.duty_cycle = 0.5f;
pwm.config.freq = 5000;
pwm.config.duty_cycle = 0.2f;
呼吸灯代码
while(1)
{
for(int i=0;i<=100;i++)
{
pwm_cfg.duty_cycle = i/100.0;
hal_pwm_para_chg(&pwm, pwm_cfg);
aos_msleep(20);
}
for(int i=100;i>=0;i--)
{
pwm_cfg.duty_cycle = i/100.0;
hal_pwm_para_chg(&pwm, pwm_cfg);
aos_msleep(20);
}
}
可以看到占空比从0~100变化。下图是99~100~99的过程。
将GPIO16接到JP8的GPIO19_LED_B,用GPIO16的占空比控制蓝色LED的亮灭。
可以看到呼吸灯的效果。实际灯的亮度并不是跟电压呈线性关系的,所以占空比和亮度也不是线性的,所以要按照实际LED的特性进行优化,按照查表得到不同亮度和占空比的对应关系,这样输出才会是均匀变化。
以上完成了PWM的测试,使用逻辑分析仪测试了占空比和频率的设置的正确性。同时用PWM控制LED实现了呼吸灯的效果。
coremark基准性能测试
原帖地址:http://www.eeworld.com.cn/aGGKqX1
按照测评计划,我们这一篇进行性能测试。CPU基准性能测试有很多种,比较常见的是coremark,我们就以coremark进行CPU的基准性能测试。
准备代码,下载代码。只保留如下内容,并添加到工程app目录下:
修改代码
core_portme.h中
#define HAS_PRINTF 0改为#define HAS_PRINTF 1
#define ee_printf printf
改为
#define ee_printf aos_cli_printf
添加#include "aos/cli.h"
添加#define MAIN_HAS_NOARGC 1
FLAGS_STR按照实际改为”-O0”或者”-O3”等。
typedef size_t ee_size_t;改为
typedef unsigned int ee_size_t;
添加#define ITERATIONS 10000
该值需要按照实际修改,如果提示运行时间不够则修改增加该值。
core_portme.c中
barebones_clock()
{
#error \
"You must implement a method to measure time in barebones_clock()! This function should return current time.\n"
}
改为
barebones_clock()
{
//#error \
// "You must implement a method to measure time in barebones_clock()! This function should return current time.\n"
return aos_sys_tick_get();
}
注释掉
#if 0
#error \
"Call board initialization routines in portable init (if needed), in particular initialize UART!\n"
#endif
以下两个宏确定获取时间tick的单位
#define TIMER_RES_DIVIDER 1
# define CLOCKS_PER_SEC (100)
core_main.c中main函数改为coremark_main
core_util.c/coremark.h还有其他文件中的crc16改为core_crc16
main函数中调用
void coremark_main(void);
coremark_main();
测试:设置优化等级。
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 10261
Total time (secs): 102.610000
Iterations/Sec : 97.456388
Iterations : 10000
Compiler version : GCC10.2.0
Compiler flags : -O3
Memory location : STACK
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0x988c
Correct operation validated. See README.md for run and reporting rules.
CoreMark 1.0 : 97.456388 / GCC10.2.0 -O3 / STACK
蓝牙播放音频测试
原帖地址:http://www.eeworld.com.cn/ai108qH
BL606P-DVK 开发板基于博流RISC-V双核 AIoT 边缘计算SOC BL606P,它具有 Wi-Fi / BT / BLE / Zigbee/Ethernet/USB2.0六模合一的多模智能网关功能,内置一颗RISC-V 32位高性能CPU,可实现多种无线有线连接和数据传输;另一颗RISC-V 64位超高性能CPU,提供超高的数据处理算力,可加速智能音箱相关算法。特别适合多媒体开发,这一篇我们就进行蓝牙音频播放测试。
安装喇叭如下:
下载代码:https://occ.t-head.cn/community/download?id=4080667575720153088
解压到非中文路径,双击打开longyuan_v2.1.1\solutions\bt_audio_demo\project.cdkproj
编译:
下载,注意JTAG跳线。
复位可以看到串口打印:
用手机扫描蓝牙,找到"BT Audio Demo" 设备点击,配对。
配对成功后串口打印如下:
串口输入以下指令播放:
bt avrcp pt 68 0
bt avrcp pt 68 1
串口打印如下:
手机中看到播放内容:
输入
bt avrcp pt 69 0
bt avrcp pt 69 1暂停
总结
提供了蓝牙音频开发环境,能基于Demo快速进行应用开发。配的3W大喇叭音质不错。
启动播放时有点爆炸声音,可以优化下。
通过蓝牙进行WIFI配网测试
原帖地址:http://www.eeworld.com.cn/aW9mvH8
WiFi 作为一种常见的无线接入手段,在使用前需要配置 SSID 和 Password。通过 wifi_provisioning 组件,开发者可以方便的实现多种配网方式。
常用的配网方式有:
SoftAP 配网,通过设备上 WiFi,生成一个临时网络,手机接入临时网络,完成配置。BLE 蓝牙配网,通过 BLE 配置 WiFi 网络。其中 BLE 蓝牙配网方案,无论是成功率还是用户体验均优于 SoftAP 配网方案。
本篇测试使用 BLE 蓝牙进行WIFI配网。
过程:安装喇叭如下。
下载代码https://occ.t-head.cn/community/download?id=4080667575720153088。
解压到非中文路径,双击打开longyuan_v2.1.1\solutions\wifi_ble_provisioning_demo\project.cdkws
编译:
下载,注意JTAG跳线
重启,串口打印如下:
使用手机,安装 NRF Connect App,点击右上角的SCAN,扫描设备名称为 "WBP_DEMO"。
点击CONNECT:
点击如下位置:
分别点击如下向上箭头:
选择TEXT输入WIFI用户名,SEND:
选择TEXT,密码,SEND:
串口可以看到连接了WIFI:
以上体验了通过蓝牙快速进行WIFI配网,这在物联网场景中是非常有用的,比通过WIFI设置热点再配置路由器的用户名和密码更快捷。
3. GD32VF103C-START
作者:常见泽1
流水灯跑跑跑
原帖地址:http://www.eeworld.com.cn/a9KaDeP
一、开盒
经历了一些波折,终于拿到了GD32VF103C START开发板,外壳依旧是GD的一贯风格,白色外壳,但是纸壳厚度明显比之前Cortex内核的开发板要厚重,盒子开关卡扣也变成了磁铁吸合的,看起来比之前GD的开发板高端。
打开盒子,映入眼帘的是乳白色的开发板,这个白色比ST NUCLEO板要更白一点,没有NUCLEO板看起来厚重,另外GD的两边排针都是空的没有焊接,ST的一般都是焊接好的。
吐槽一下,GD的RISC-V板子过孔也太多了 而且白色板子就特别特别明显,我个人看起来不是特别的舒服。(看图片还可以,实物是有点不太舒服的)
二、软件安装
Windows环境下搭建基于Eclipse + RISC-V gcc编译器的RISC-V开发环境,配合openocd调试软件,可以实现RISC-V内核程序的编译、下载和调试。下载直接去的芯来科技官网下载的开发环境。
安装过程中碰到个小问题:
Eclipse启动提示javaw.exe in your current PATH
可修改eclipse.ini 文件,在最前面加上下面两行内容:
-vm
D:/jva/bin/javaw.exe
三、工程建起来
点击FINISH即可。
产生的例程是EVAL板的,而我用的是START板,GPIO略有区别,需要修改修改。
查看原理图,修改部分代码
gd_eval_led_init(LED1);
// gd_eval_led_init(LED2);
// gd_eval_led_init(LED3);
// gd_eval_led_init(LED4);
while(1){
/* turn on led1, turn off led4 */
gd_eval_led_on(LED1);
//gd_eval_led_off(LED2);
delay_1ms(1000);
/* turn on led2, turn off led1 */
// gd_eval_led_on(LED2);
gd_eval_led_off(LED1);
delay_1ms(1000);
}
编译真的比较慢,下载仿真调试更更更慢了!可以喝一杯茶再来了。看现象LED开始闪烁,OK 第一个例程成功。
和ST官方例程简单比较
原帖地址:http://www.eeworld.com.cn/aL0ubPK
粗略看了下GD的官方例程,感觉还是保持了ST官方例程的大体结构,基本做过ST开发的很容易能明白。基本差不多,除了库的位置放置略不同。
下面这是ST的官方例程的目录:
我们打开GD的官方例程:
A.GD32VF103_standard_peripheral子文件夹
1.固件库头文件
2.固件库源文件
3.GD32VF103全局头文件和系统配置文件
B.GD32VF103_standard_peripheral子文件夹
C. RSIC-V文件夹
1.drivers子文件夹包含RISC-V内核的支持文件
2. env_Eclipse子文件夹包含了基于RISC-V内核处理器的启动代码、 异常服务程序及链接脚
本文件
3. stubs子文件夹包含了_write、 _read等桩函数的定义
工程放置位置:
打开key中断例程工程文件夹:
1.gd32vf103_it.c/.h
中断处理程序及头文件,和ST的类似
2.gd32vf103_libopt.h
设置例程所使用的外设,包含了什么adc.h gpio.h头文件,感觉有点像ST的conf.h那个头文件(个人感觉,可能并不是这个意思)
3.main.c
用户的例程
4.systick.c
使用systick的精准延时程序
代码对比
简单拿一个GPIO例程来,GD RISC-V的和ST M3的很相似,只是库函数的名字改了,基本参数名字改了,时钟和IO口需要配置的东西基本类似。
GD的GPIO函数库如下:
所以我们编程只要抓住GD的库函数手册就可以啦,就是GD32VF103 固件库用户指南.pdf。
RISC-V初步了解
原帖地址:http://www.eeworld.com.cn/aO0aDGS
RISC-V(发音同“risk-five”)是一种免费开源指令集架构(ISA),通过开放标准协作开创处理器创新的崭新纪元。RISC-V基金会创立 于2015年,由超过235家成员组织组成,建立了首个开放、协作的软硬件创新者社区,开创了处理器创新的新时代。RISC-V ISA发端于深厚的学术研究,将免费且可扩展的软硬件架构自由度提升至新的水平,为未来50年的计算设计与创新铺平了道路,国产CPU逆袭之路。
RISC-V是由U.C. Berkeley开发的自由和模块化的RISC指令集,“V”包含两层意思,一是这是Berkeley从RISC I开始设计的第五代指令集架构,二是它代表了变化(variation)和向量(vectors)。
不同于x86、Arm架构高昂的IP费用,RISC-V架构使用BSD开源协议给予使用者很大自由,允许使用者修改和重新发布开源代码,也允许基于开源代码开发商业软件发布和销售。
GD32VF103采用RISC-V的Bumblebee处理器内核。Bumblebee内核设计了二级变长流水线微架构,配备了精简的指令预取单元和动态分支预测器,并融入多种低功耗设计方法,能够以二级流水线的代价,达到传统架构三级流水线的性能和频率,实现了优秀的能效比与成本优势。所以GD32VF103在最高主频下的工作性能可达153 DMIPS,CoreMark测试也取得了360分的优异表现,相比GD32 Cortex-M3内核产品性能提升15%的同时,动态功耗还降低了50%,待机功耗更是降低了25%。
指令集就是一个处理器的硬件可以支持的基本操作(符号化的抽象描述)的集合。(wiki:“An instruction set, with its instruction set architecture (ISA), is the interface between a computer's software and its hardware, and thereby enables the independent development of these two computing realms; it defines the valid instructions that a machine may execute.”)。
通常,处理器的指令集架构(ISA:Instruction Set Architecture)确定它的功能。最著名的x86就是intel CPU的指令集。一个通用处理器,为了适应所有的应用,其指令集必须考虑最大的灵活性。这种灵活性主要表现在指令功能是不是完备和粒度是不是够细。
RISC-V的不同寻常之处,除了在于它是最近诞生的和开源的以外,还在于:和几乎所有以往的ISA不同,它是模块化的。它的核心是一个名为RV32I的基础ISA,运行一个完整的软件栈。RV32I是固定的,永远不会改变。这为编译器编写者,操作系统开发人员和汇编语言程序员提供了稳定的目标。模块化来源于可选的标准扩展,根据应用程序的需要,硬件可以包含或不包含这些扩展。这种模块化特性使得RISC-V具有了袖珍化、低能耗的特点,而这对于嵌入式应用可能至关重要。RISC-V编译器得知当前硬件包含哪些扩展后,便可以生成当前硬件条件下的最佳代码。惯例是把代表扩展的字母附加到指令集名称之后作为指示。例如, RV32IMFD将乘法(RV32M),单精度浮点(RV32F)和双精度浮点(RV32D)的扩展添加到了基础指令集(RV32I)中。
架构师希望保持 ISA 的简洁性,从而缩小实现 ISA 的处理器的尺寸。我们将在随后的章节看到, RISC-V ISA 比 ARM-32 ISA 简洁得多。就简洁性造成的影响举例,我们把使用相同大小缓存(16KiB)的 RISC-V Rocket 处理器和采用相同技术(TSMC40GPLUS)的 ARM-32 Cortex A5 处理器进行比较。RISC-V 晶粒的大小是 0.27mm2,而 ARM-32 晶粒的大小是0.53mm2。由于面积大一倍, ARM-32 Cortex A5 的晶粒成本是 RISC-V Rocket 的约 4(22)倍。即使晶粒的大小只缩小 10%,成本也将以 1.2(1.12)倍的比例缩小。
ARM-32 的架构师后来试图通过向以前统一的 32 位 ISA 中添加 16 位指令来缩减代码长度,但根本就没有空间了。因此,唯一的解决方案是先用 16 位指令来创建一个新的 ISA(Thumb),然后同时用 16 位指令和 32 位指令来组成另外一个 ISA(Thumb-2),并用一个模式位在两种长度的指令间切换。为了切换模式,程序员或编译器会跳转到一个最低有效位为 1 的字节地址。这种方法有效的前提是,在正常的 16 位和 32 位指令中, 该位应该是 0
RISC-V和Corex-M3指令测试
原帖地址:http://www.eeworld.com.cn/aDK84GC
加减乘除运算应该是每个人编程都会用得到,本篇就想看看RSIC-V和M3的加减乘除取余等等运算有什么区别。
乘法运算:
M3
RSIC-V
简单的例子反汇编:
RSIC-V反汇编
除法运算:
长期以来, ARM-32 只有乘法而无除法指令。直到第一台 ARM 处理器诞生的大约 20 年后(2005 年),除法指令才成为 ARM 的必要组成部分
Cortex-M3包含两个硬件除法指令, SDIV 和 UDIV(Thumb-2 指令)
RSIC-V带除法指令
M3 整型除法汇编
RSIC-V 浮点数除法汇编运算
取余运算:
RV32M 具有有符号和无符号整数的除法指令:divide(div)和 divide unsigned(divu),它们将商放入目标寄存器。在少数情况下,程序员需要余数而不是商,因此 RV32M 提供remainder(rem)和 remainder unsigned(remu),它们在目标寄存器写入余数,而不是商。
而M3是没有取余的,需要通过乘法和加减一起来实现。
试验跑起来
由于我逻辑分析仪是20M采样率,我把RSIC-V和M3的主频都设置在了24MHZ,否则测的不会很准,毕竟乘除法可能只是几个指令周期的活。
(1)num=i*j+i*k 乘法 I j 整型数据
(2)num=i*j 乘法 I j float型数据
(3)num = i/j +i/k除法 I j是整型数据
M3是硬件除法器,在整型除法里耗时比RISC-V要短。
(4)num = i/j+I/K 除法 I j是float型数据
在浮点数除法运算力,RISC-V的表现是快了一半。
(5)i%j+i%k整数取余
虽然RISC-V的取余是直接得到,和M3不一样,但是耗时却更加长了,不知道改的意义在哪里???
总体来说,RISC-V和Cortex-M3在同等频率、同等运算下进行测试,很多运算指令执行速度要相对快一些。
START PWM
原帖地址:http://www.eeworld.com.cn/aGWDe5O
先来看下RISC-V GD的103的定时器的总览:
这次主要参考官方例程,使用PWM模式:
在 PWM 输出模式下(PWM 模式 0 是配置 CHxCOMCTL 为 3’b110, PWM 模式 1 是配置
CHxCOMCTL 为 3’b111),通道根据 TIMERx_CAR 寄存器和 TIMERx_CHxCV 寄存器的值,
输出 PWM 波形。
根据计数模式,可以分为两种 PWM 波:EAPWM(边沿对齐 PWM)和 CAPWM(中央对齐 PWM)
参考M3的PMW描述,两者是差不多的:
这里我们选用PWM模式0.
下面代码开始配置PWM的时钟、GPIO、模式及参数:
这里分频系数119 周期500 主频是108M 算下来要你管管是1.8khz左右,占空比50%
其实和M3真的很像,见下图。
基本一样,主要就是在通道参数配置那里略有不同,貌似多了两个函数
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 250);
timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0);
而M3是由结构体完成初始化
我们看下试验现象
START EXTI中断
原帖地址:http://www.eeworld.com.cn/anz5aT4
RISC-V: EXTI(中断/事件控制器)包括19个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。EXTI有三种触发类型:上升沿触发、下降沿触发和任意沿触发。EXTI中的每一个边沿检测电路都可以独立配置和屏蔽
STM32:EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
看一下RISC-V EXTI框图
Cortex EXTI图
上图就可以看出不一样了,STM32是通过NVIC有个,而RISC采用了ECLIC
RISC-V集成了改进型内核中断控制器(ECLIC)来实现高效的异常和中断处理。ECLIC旨在为RISC-V系统提供低延迟、矢量化、 抢占式的中断。 当ECLIC被激活后, 它将包含并替换现有的RISC-V处理器局部中断控制器。CLIC设计有一个基本设计,需要最少的硬件,但它支持额外的扩展来提供硬件加速。ECLIC设计的目标是为各种软件ABI和中断模型提供支持, 而不需要复杂的硬件影响高性能处理器的实现。它与处理器核心紧密耦合。
RSIC-V
Cortex图
Cortex配置的步骤:
通过下面的过程来配置20个线路做为中断源:
● 配置20个中断线的屏蔽位(EXTI_IMR)
● 配置所选中断线的触发选择位(EXTI_RTSR和EXTI_FTSR);
● 配置对应到外部中断控制器(EXTI)的NVIC中断通道的使能和屏蔽位,使得20个中断线中的请求可以被正确地响应
RISC-V 中断配置
Cortex 中断配置
下面就和RISC不一样了,RISC中断等级等等都在ECLIC,而CORTEX还得配置下NVIC
中断函数
RISC-V
Cortex-M3
这个没啥说的,基本都一样句柄。
希望通过三款产品的深度评测,帮助工程师快速了解三款产品。未来,我们还会继续深度评测更多国产RISC-V MCU,敬请期待。
· END ·