I am working for a cross-platform desktop application . I develop it in C++ with usage of Qt and already implemented PC side Omron FINS TCP protocol driver via sockets
( QTcpSocket ).
When I read from register via FINS protocol - I receive right response within 30ms ok , but when I write to plc's registers - there is no response from the plc - socket timeout error ( QAbstractSocket error code = 5 ).
Code for reading from register ( function receives reg_addr - address of a register and updates value ):
bool fins_read_reg(int reg_addr,int *value);
-->
static unsigned char fins_cmnd[MAX_MSG], fins_resp[MAX_MSG],fins_tcp_header[MAX_HEADER];
static unsigned char fins_send_frame[34], fins_receive_frame[32];
static int sendlen, recvlen;
//char sid = 0;
if(sid > 0xFF)
sid = 0;
if(f!=0)
{
fprintf(f,"BEGIN TO READ \n");
fprintf(f," reg_addr = %d \n",reg_addr);
fprintf(f," value = %d \n",*value);
}
/* SEND FINS/TCP COMMAND*/
/*
* GENERATE FINS COMMAND FRAME
*/
fins_send_frame[0] = 'F'; /* Header */
fins_send_frame[1] = 'I';
fins_send_frame[2] = 'N';
fins_send_frame[3] = 'S';
fins_send_frame[4] = 0x00; /* Length */
fins_send_frame[5] = 0x00;
fins_send_frame[6] = 0x00;
fins_send_frame[7] = 18+8; /*Length of data from Command up to end of FINS frame */
fins_send_frame[8] = 0x00; /* Command */
fins_send_frame[9] = 0x00;
fins_send_frame[10] = 0x00;
fins_send_frame[11] = 0x02;
fins_send_frame[12] = 0x00; /* Error Code */
fins_send_frame[13] = 0x00;
fins_send_frame[14] = 0x00;
fins_send_frame[15] = 0x00;
//command:
fins_send_frame[16] = 0x80; /* ICF */ //put 0x00 - response required //was 0x80
fins_send_frame[17] = 0x00; /* RSV */
fins_send_frame[18] = 0x03; /* GCT */
fins_send_frame[19] = 0x00; /* DNA */
fins_send_frame[20] = srv_node_no; /* DA1 *//* Ethernet Unit FINS NODE NUMBER*/
fins_send_frame[21] = 0x00; /* DA2 */
fins_send_frame[22] = 0x00; /* SNA */
fins_send_frame[23] = cli_node_no; /* SA1 *//* WS FINS NODE NUMBER OBTAINED
AUTOMATICALLY*/
fins_send_frame[24] = 0x00; /* SA2 */
fins_send_frame[25] = sid; //++sid; /* SID */
fins_send_frame[26] = 0x01; /* MRC */ //1
fins_send_frame[27] = 0x01; /* SRC */ //read
// change to read:
fins_send_frame[28] = memory_area_designation_code(reg_addr); /* VARIABLE TYPE: DM*/
if(f!=0)
{
fprintf(f,"FINS/TCP memory area designation code = %d \n",fins_cmnd[28]);
}
if(memory_area_designation_code(reg_addr) == 0xB0) //-0x0c000
{
fins_send_frame[29] = (unsigned char)((reg_addr-0x0C000)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr-0x0C000)&0xFF); //low register
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0xB1) //-0x0DE00
{
fins_send_frame[29] = (unsigned char)((reg_addr-0x0DE00)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr-0x0DE00)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0x82) //-0x10000
{
fins_send_frame[29] = (unsigned char)((reg_addr-0x10000)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr-0x10000)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0xB3) //-0x0BA00
{
fins_send_frame[29] = (unsigned char)((reg_addr-0x0BA00)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr-0x0BA00)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
fins_send_frame[31] = 0x00;//bit number
//number of words to read:
fins_send_frame[32] = 0x00; /* WORDS READ: 1*/
fins_send_frame[33] = 0x01;
/* SEND FINS/TCP COMMAND*/
sendlen = 34;
sc.write((const char*)fins_send_frame,sendlen);
if(sc.waitForBytesWritten(RESP_TIMEOUT))
{
}
else
{
if(f!=0)
{
fprintf(f,"2.error in sending FINS send frame of command %d \n",sc.error());
}
return false;
}
//lets try to get response here
if(sc.waitForReadyRead(RESP_TIMEOUT))
{
if(f!=0)
{
fprintf(f,"2.received %d \n",recvlen = sc.bytesAvailable());
}
sc.read((char*)fins_receive_frame,recvlen);
//print received header:
if(f!=0)
{
for(int i =0;i<recvlen;i++)
{
}
}
//check for errors:
if(((int)fins_receive_frame[28] == 0) && ((int)fins_receive_frame[29] == 0) )
{
//command sent ok
sid++;
//lets get value:
*value = (int)((0x0000|fins_receive_frame[30])<<8) + (int)
(0x00FF&fins_receive_frame[31]);
if(f!=0)
{
//lets get value:
fprintf(f,"2.* value = %d \n",value);
}
return true;
}
else
{
//there were errors when sending
*value = 0;
if(f!=0)
{
//lets get value:
fprintf(f,"fields 28 and 29 are not null \n");
}
return false;
}
}
else
{
//can't get response - error:
//no response:
if(f!=0)
{
fprintf(f,"2.no response %d \n",sc.error());
}
return true;
}
***
Code snippet of writing to register ( function receives reg_addr - address of a register and value to put to that register ):
bool fins_write_reg(int reg_addr,const int value);
-->
static unsigned char fins_cmnd[MAX_MSG], fins_resp[MAX_MSG],fins_tcp_header[MAX_HEADER];
static unsigned char fins_send_frame[36], fins_receive_frame[30];
static unsigned char srv_node_no, cli_node_no;
static int sendlen, recvlen;
// char sid = 0;
if(sid > 0xFF)
sid = 0;
if(f!=0)
{
fprintf(f,"BEGIN TO WRITE \n");
fprintf(f," reg_addr = %d \n",reg_addr);
fprintf(f," value = %d \n",value);
}
/* SEND FINS/TCP COMMAND*/
/*
* GENERATE FINS COMMAND FRAME
*/
//fins header body
fins_send_frame[0] = 'F'; /* Header */
fins_send_frame[1] = 'I';
fins_send_frame[2] = 'N';
fins_send_frame[3] = 'S';
fins_send_frame[4] = 0x00; /* Length */
fins_send_frame[5] = 0x00;
fins_send_frame[6] = 0x00;
fins_send_frame[7] = 0x1C; //20+8;
fins_send_frame[8] = 0x00; /* Command */
fins_send_frame[9] = 0x00;
fins_send_frame[10] = 0x00;
fins_send_frame[11] = 0x02;
fins_send_frame[12] = 0x00; /* Error Code */
fins_send_frame[13] = 0x00;
fins_send_frame[14] = 0x00;
fins_send_frame[15] = 0x00;
//fins command body
fins_send_frame[16] = 0x80; /* ICF */
fins_send_frame[17] = 0x00; /* RSV */
fins_send_frame[18] = 0x02; /* GCT */
fins_send_frame[19] = 0x00; /* DNA */
fins_send_frame[20] = srv_node_no; /* DA1 *//* Ethernet Unit FINS NODE NUMBER*/
fins_send_frame[21] = 0x00; /* DA2 */
fins_send_frame[22] = 0x00; /* SNA */
fins_send_frame[23] = cli_node_no; /* SA1 *//* WS FINS NODE NUMBER
AUTOMATICALLY*/
fins_send_frame[24] = 0x00; /* SA2 */
fins_send_frame[25] = sid; /* SID */
fins_send_frame[26] = 0x01; /* MRC */ //1
fins_send_frame[27] = 0x02; /* SRC */ //1 - write
// change to write:
fins_send_frame[28] = memory_area_designation_code(reg_addr); /* VARIABLE TYPE: DM*/
if(f!=0)
{
}
if(memory_area_designation_code(reg_addr) == 0xB0) //-0x0c000
{
fins_send_frame[29] = (unsigned char)((reg_addr - 0x0c000)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr - 0x0c000)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0xB1) //-0x0DE00
{
fins_send_frame[29] = (unsigned char)((reg_addr - 0x0DE00)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr - 0x0DE00)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0x82) //-0x10000
{
fins_send_frame[29] = (unsigned char)((reg_addr - 0x10000)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr - 0x10000)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
if(memory_area_designation_code(reg_addr) == 0xB3) //-0x0BA00
{
fins_send_frame[29] = (unsigned char)((reg_addr - 0x0BA00)>>8);
fins_send_frame[30] = (unsigned char)((reg_addr - 0x0BA00)&0xFF);
if(f!=0)
{
fprintf(f,"fins_send_frame[29] = %d \n",fins_send_frame[29]);
fprintf(f,"fins_send_frame[30] = %d \n",fins_send_frame[30]);
}
}
fins_send_frame[31] = 0x00;//bit number
//number of words to read:
fins_send_frame[32] = 0x00; /* number of data words */
fins_send_frame[33] = 0x01;
//putting actual write value:
fins_send_frame[34] = (unsigned char)(value >> 8); //high byte
fins_send_frame[35] = (unsigned char)(value&0xFF); //low byte
//displaying what is written:
if(f!=0)
{
fprintf(f,"fins_send_frame[34] = %d \n",fins_send_frame[34]);
fprintf(f,"fins_send_frame[35] = %d \n",fins_send_frame[35]);
}
sendlen = 36;
/* SEND FINS/TCP COMMAND*/
sc.write((const char*)fins_send_frame,sendlen);
if(sc.waitForBytesWritten(RESP_TIMEOUT))
{
if(f!=0)
{
}
}
else
{
if(f!=0)
{
fprintf(f,"2.error in sending FINS tcp send frame of command %d \n",sc.error());
}
return false;
}
//sc.flush();
//lets try to get response here :
if(sc.waitForReadyRead(RESP_TIMEOUT)) // here it is RESPONSE TIMEOUT ERROR
{
if(f!=0)
{
fprintf(f,"2.received %d \n",recvlen = sc.bytesAvailable());
}
sc.read((char*)fins_receive_frame,recvlen);
if(f!=0)
{
for(int i =0;i<recvlen;i++)
{
fprintf(f,"2.received fins_receive_frame[%d] = %d \n",i,fins_receive_frame[i]);
}
}
//basic check - no error
if(((int)fins_receive_frame[28] == 0) && ((int)fins_receive_frame[29] == 0))
{
//command sent ok
sid++;
if(f!=0)
{
fprintf(f,"receive response does not contain errors");
}
return true;
}
else
{
//there were errors when sending
if(f!=0)
{
fprintf(f,"receive response contains errors");
}
return false;
}
}
else //cannot receive response
{
if(f!=0)
{
fprintf(f,"3.no response %d \n",sc.error());
}
return true;
}
where f - is some temporary log file pointer
sc - QTcpSocket instance that send/receives FINS commands
PLC:
CP1L-L14DT1-D
Ethernet option board : CP1W-CIF41
memory area designation code function:
int FinsOverEthCP::memory_area_designation_code(const int addr)
{
// area codes
if(addr>0x0BFFF&&addr<0x0D800) //B0 , CIO area
return 0xB0;
if(addr>0x0DDFF&&addr<0xE000) //work area
return 0xB1; //TODO: clarified code , from memory area designation codes table
if(addr>0xFFFF&&addr<0x18000) //DM area
return 0x82;
if(addr>0xB9FF&&addr<0xBC00) //A448 - A959
return 0xB3;
else
return 0x80; //CIO,LR,HR,AR area
}
When I tested the command using Multiway software - the response is received and value putted to register , but the same with QTcpSocket and the error, why?
My question to you: Have you such problem come up when you run your FINS driver? Why no response situation can be while FINS write to register command is formed ok? If yes how to overcome/fix?
I created and used this library in few industrial project, so it is stable. But lack of documentation.
https://bitbucket.org/vladimirek/omronlib
Related
I am trying to initialize my W25Q128jv External Flash in a my project, but I am not able to write on the external flash and I think it could be because of the Memory Mapping Mode but I really don't know.
static void MX_QUADSPI_Init(void)
{
/* USER CODE BEGIN QUADSPI_Init 0 */
/* USER CODE END QUADSPI_Init 0 */
/* USER CODE BEGIN QUADSPI_Init 1 */
/* USER CODE END QUADSPI_Init 1 */
/* QUADSPI parameter configuration*/
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 1;
hqspi.Init.FifoThreshold = 4;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = 23;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.FlashID = QSPI_FLASH_ID_1;
hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN QUADSPI_Init 2 */
BSP_QSPI_Init();
/* USER CODE END QUADSPI_Init 2 */
}
Here the driver:
uint8_t BSP_QSPI_Init(void) { QSPI_CommandTypeDef s_command; uint8_t value = W25Q128JV_FSR_QE;
/* QSPI memory reset */ if (QSPI_ResetMemory() != QSPI_OK) { return QSPI_NOT_SUPPORTED; }/* Enable write operations */
if (QSPI_WriteEnable() != QSPI_OK)
{
return QSPI_ERROR;
}
/* Set status register for Quad Enable,the Quad IO2 and IO3 pins are enable */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = WRITE_STATUS_REG2_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.NbData = 1; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Configure the command / if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } / Transmit the data */ if (HAL_QSPI_Transmit(&hqspi, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
/* automatic polling mode to wait for memory ready */ if (QSPI_AutoPollingMemReady(W25Q128JV_WRITE_STATUS_REG_TIME_MS) != QSPI_OK) { return QSPI_ERROR; } BSP_QSPI_MemoryMappedMode(); //BSP_QSPI_Erase(0x90000000, 1000); return QSPI_OK; } /BSP_QSPI_Init/
static uint8_t QSPI_ResetMemory(void) { QSPI_CommandTypeDef s_command;
/* Initialize the reset enable command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = RESET_ENABLE_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Send the command */ if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
/* Send the reset memory command */ s_command.Instruction = RESET_MEMORY_CMD; if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
/* Configure automatic polling mode to wait the memory is ready */ if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) { return QSPI_ERROR; }
return QSPI_OK; }
static uint8_t QSPI_WriteEnable(void) {
QSPI_CommandTypeDef sCommand;
QSPI_AutoPollingTypeDef sConfig;
/* Enable write operations ------------------------------------------ */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.Instruction = WRITE_ENABLE_CMD;
sCommand.AddressMode = QSPI_ADDRESS_NONE;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DataMode = QSPI_DATA_NONE;
sCommand.DummyCycles = 0;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
/* Configure automatic polling mode to wait for write enabling ---- */
sConfig.Match = W25Q128JV_FSR_WREN;
sConfig.Mask = W25Q128JV_FSR_WREN;
sConfig.MatchMode = QSPI_MATCH_MODE_AND;
sConfig.StatusBytesSize = 1;
sConfig.Interval = 0x10;
sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
sCommand.Instruction = READ_STATUS_REG1_CMD;
sCommand.DataMode = QSPI_DATA_1_LINE;
sCommand.NbData = 1;
if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
return (QSPI_OK);
} /* QSPI_WriteEnable */
static uint8_t QSPI_AutoPollingMemReady(uint32_t Timeout) {
QSPI_CommandTypeDef s_command;
QSPI_AutoPollingTypeDef s_config;
/* Configure automatic polling mode to wait for memory ready */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_STATUS_REG1_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
s_config.Match = 0x00;
s_config.Mask = W25Q128JV_FSR_BUSY;
s_config.MatchMode = QSPI_MATCH_MODE_AND;
s_config.StatusBytesSize = 1;
s_config.Interval = 0x10;
s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, Timeout) != HAL_OK) {
return (QSPI_ERROR);
}
return (QSPI_OK);
} /* QSPI_AutoPollingMemReady */
static uint8_t BSP_QSPI_Write(const uint8_t * pData, uint32_t WriteAddr, uint32_t Size) {
QSPI_CommandTypeDef s_command;
uint32_t end_addr, current_size, current_addr;
/* Calculation of the size between the write address and the end of the page */
current_addr = 0U;
while (current_addr <= WriteAddr) {
current_addr += W25Q128JV_PAGE_SIZE;
}
current_size = current_addr - WriteAddr;
/* Check if the size of the data is less than the remaining place in the page */
if (current_size > Size) {
current_size = Size;
}
/* Initialize the address variables */
current_addr = WriteAddr;
end_addr = WriteAddr + Size;
/* Initialize the program command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QUAD_INPUT_PAGE_PROG_CMD;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_4_LINES;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Perform the write page by page */
do {
s_command.Address = current_addr;
s_command.NbData = current_size;
/* Enable write operations */
QSPI_WriteEnable();
/* Configure the command */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
/* Transmission of the data */
if (HAL_QSPI_Transmit(&hqspi, (uint8_t *)((uint32_t)pData), HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
/* Configure automatic polling mode to wait for end of program */
if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) {
return (QSPI_ERROR);
}
/* Update the address and size variables for next page programming */
current_addr += current_size;
pData += current_size;
current_size = ((current_addr + W25Q128JV_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : W25Q128JV_PAGE_SIZE;
} while (current_addr < end_addr);
return (QSPI_OK);
} /* BSP_QSPI_Write */
uint8_t BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size) {
QSPI_CommandTypeDef s_command;
/* Initialize the read command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_CMD;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.Address = ReadAddr;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.NbData = Size;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Configure the command */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
/* Reception of the data */
if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return (QSPI_ERROR);
}
return (QSPI_OK);
} /* BSP_QSPI_Read */
static void BSP_QSPI_MemoryMappedMode(void) {
QSPI_CommandTypeDef s_command;
QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;
/* Configure the command for the read instruction */
s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;
s_command.Address = 0U;
s_command.AlternateBytes = 0xF0;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
s_command.DummyCycles = W25Q128JV_DUMMY_CYCLES_READ_QUAD;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.AddressMode = QSPI_ADDRESS_4_LINES;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES;
s_command.DataMode = QSPI_DATA_4_LINES;
s_command.NbData = 0U;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Configure the memory mapped mode */
s_mem_mapped_cfg.TimeOutPeriod = 0;
s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
if (HAL_QSPI_MemoryMapped(&hqspi, &s_command, &s_mem_mapped_cfg) != HAL_OK) {
Error_Handler();
}
} /* BSP_QSPI_MemoryMappedMode */
static uint8_t BSP_QSPI_Erase(uint32_t Address, uint32_t Size) {
#define TIMEOUTSAFE_MS 1000U
QSPI_CommandTypeDef s_command;
uint16_t Timeout;
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
if (Size > W25Q128JV_SECTOR_SIZE) {
s_command.Instruction = BLOCK_64_ERASE_CMD;
} else {
s_command.Instruction = SECTOR_ERASE_CMD;
}
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.Address = Address;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Enable write operations */
if (QSPI_WriteEnable() != QSPI_OK) {
return (QSPI_ERROR);
}
/* Send the command */
QSPI_Cplt = 0U;
Timeout = TIMEOUTSAFE_MS;
if (HAL_QSPI_Command_IT(&hqspi, &s_command) != HAL_OK) {
return (QSPI_ERROR);
}
while ((QSPI_Cplt == 0U) && (Timeout > 0U)) {
HAL_Delay(1000);
Timeout--;
}
if (Timeout == 0U) {
return (QSPI_ERROR);
}
if (s_command.Instruction == BLOCK_64_ERASE_CMD) {
Timeout = W25Q128JV_BLOCK_64_ERASE_MAX_TIME;
} else if (s_command.Instruction == BLOCK_32_ERASE_CMD) {
Timeout = W25Q128JV_BLOCK_32_ERASE_MAX_TIME;
} else {
Timeout = W25Q128JV_SECTOR_ERASE_MAX_TIME;
}
/* Configure automatic polling mode to wait for end of erase */
if (QSPI_AutoPollingMemReady(Timeout) != QSPI_OK) {
return (QSPI_ERROR);
}
return (QSPI_OK);
} /* BSP_QSPI_Erase*/
For this project I will need my QUADSPI when I will add TouchGFX to save some items, but for now I am only trying to do a for cycle to verify that it works and its performance
uint32_t *flashbuffer = (uint32_t*)0x90000000;
const uint32_t size = 1000;
int begin = HAL_GetTick();
for(i = 0; i < size; i++)
{
flashbuffer[i]=i;
}
int end = HAL_GetTick();
I want to use HTTP2 to POST data continuously. As I found, the only feasible solution is to use shlib. I can implement it and use it. But there were two problems that I faced:
1- shlib does not let us send a data bigger than 16KB theoretically. Here, the solution that I found was to feed the buffer couple of times without calling NGHTTP2_DATA_FLAG_EOF. But, the main problem is that we cannot return the size of the buffer which although is defined as int, but does not support lengths more than 16K.
2- The fault rate of sending data more than about 3 to 4K goes exponentially high as in these situations, just a few of packets are able to be sent correctly.
Any suggestion?
Thanks
I did all my bests to make sure that the resources don't interfere with each other. Here is my code:
#include <Arduino.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
extern "C"
{
#include "sh2lib.h"
}
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
// CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
const char* ssid = "NETWORK"; // your network SSID (name of wifi network)
const char* password = "PASSWORD"; // your network password
bool request_finished = false;
String head = "--JPEG_IMAGE\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
String tail = "\r\n--JPEG_IMAGE--\r\n";
char data_to_post[16000];
uint32_t totalLen;
camera_config_t config;
struct sh2lib_handle hd;
bool is_captured;
bool is_posted;
uint16_t safety_counter;
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
delay(100);
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// attempt to connect to Wifi network:
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
// wait 1 second for re-trying
delay(1000);
}
Serial.print("\n");
Serial.print(F("Connected to: "));
Serial.println(ssid);
// Etablishing Connection
Serial.println(F("Establishing Connection... "));
if (sh2lib_connect(&hd, "My_Server") != ESP_OK)
{
Serial.println("Error connecting to HTTP2 server");
//vTaskDelete(NULL);
}
Serial.println(F("Connected to the webserver"));
delay(1000);
// Configuring the Cam
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
// init with high specs to pre-allocate larger buffers
if(psramFound())
{
config.frame_size = FRAMESIZE_VGA;// FRAMESIZE_QVGA
config.jpeg_quality = 10; //0-63 lower number means higher quality
config.fb_count = 2;
}
else
{
config.frame_size = FRAMESIZE_CIF;
config.jpeg_quality = 12; //0-63 lower number means higher quality
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK)
{
Serial.printf("Camera init failed with error 0x%x", err);
delay(1000);
ESP.restart();
}
// This task handles the POST requests
xTaskCreatePinnedToCore(
task_http2
, "http2_task"
, (1024 * 24) // Stack size
, NULL
, 3 // Priority
, NULL
, ARDUINO_RUNNING_CORE);
Serial.println(F("Task Called"));
}
void loop()
{
// To prohibit the interference between resources, I used main loop to capture the Images
// Check if the posting has been finished
if (is_posted)
{
Serial.println(F("Call to Capture"));
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb)
{
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
}
// to check if the size is not bigger than 16K
uint32_t imageLen = fb->len;
if(imageLen<16000)
{
// Creating the body of the post
uint32_t extraLen = tail.length()+head.length();
totalLen = extraLen + imageLen;
uint8_t *fbBuf = fb->buf;
const char* head_char = head.c_str();
const char* tail_char = tail.c_str();
uint32_t totalLen_copy = totalLen;
char alpha[totalLen];
std::copy(head_char,head_char+head.length(), data_to_post);
std::copy(fbBuf,fbBuf+imageLen , data_to_post+head.length());
std::copy(tail_char,tail_char+ tail.length(), data_to_post+head.length()+imageLen);
esp_camera_fb_return(fb);
Serial.println(F("Camera captured"));
delay(100);
safety_counter++;
// Stopping capturing until posting is finished
is_captured = true;
is_posted = false;
}
}
delay(100);
}
int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
{
if (len > 0)
{
Serial.printf("%.*s\n", len, data);
}
if (flags == DATA_RECV_RST_STREAM)
{
Serial.println("STREAM CLOSED");
}
return 0;
}
int handle_post_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
{
if (len > 0) {
Serial.printf("%.*s\n", len, data);
// decreasing the counter to prevent fault loop
safety_counter--;
}
//Serial.print(F("Safety_Counter in Response: ")); Serial.println(safety_counter);
if (flags == DATA_RECV_RST_STREAM) {
request_finished = true;
Serial.println("STREAM CLOSED");
}
return 0;
}
int send_post_data(struct sh2lib_handle *handle, char *buf, size_t length, uint32_t *data_flags)
{
// To check the body of the post
/*
Serial.println("Post Buffer");
for(int i;i<totalLen;i++)
Serial.print(data_to_post[i]);
Serial.println("Post Buffer End");
*/
if (totalLen < length)
{
memcpy(buf, data_to_post, totalLen);
}
else
{
Serial.println("Cannot write to buffer");
//copylen = 0;
}
(*data_flags) |= NGHTTP2_DATA_FLAG_EOF;
return totalLen;
}
void task_http2(void *args)
{
Serial.println(F("Task Runs"));
// Start capturing
is_posted = true;
int counter = 0;
for(;;)
{
// if capturing finished:
if(is_captured)
{
// after each five unsuccessful posts, reestablish the connection
Serial.print(F("Safety_Counter is: ")); Serial.println(safety_counter);
if(safety_counter>5)
{
is_posted = false;
vTaskDelay(100);
counter = 0;
safety_counter = 0;
sh2lib_free(&hd);
vTaskDelay(100);
Serial.println(F("Safety Counter Occured ... "));
if (sh2lib_connect(&hd, "My_Server") != ESP_OK)
{
Serial.println("Error connecting to HTTP2 server");
//vTaskDelete(NULL);
}
Serial.println(F("Connected to the webserver"));
vTaskDelay(1000);
// Preparing capturing again
is_posted = true;
is_captured = false;
continue;
}
char len[20];
sprintf(len, "%d",totalLen); //length_of_body);
Serial.print("the length is: ");
Serial.println(len);
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd.hostname),
SH2LIB_MAKE_NV(":path", "/mvp/upload_image"),
SH2LIB_MAKE_NV("Content-Length", len),
SH2LIB_MAKE_NV("Content-Type", "multipart/form-data; boundary=JPEG_IMAGE")
};
sh2lib_do_putpost_with_nv(&hd, nva, sizeof(nva) / sizeof(nva[0]), send_post_data, handle_post_response);
while (1)
{
if (sh2lib_execute(&hd) != ESP_OK)
{
Serial.println("Error in execute");
break;
}
if (request_finished)
{
// a general counter to reestablish the connection
counter++;
break;
}
//vTaskDelay(1000);
}
}
// General counter
if(counter>30)
{
counter = 0;
sh2lib_free(&hd);
vTaskDelay(100);
Serial.println(F("Establishing Connection... "));
if (sh2lib_connect(&hd, "My_Server") != ESP_OK)
{
Serial.println("Error connecting to HTTP2 server");
//vTaskDelete(NULL);
}
Serial.println(F("Connected to the webserver"));
}
is_captured = false;
is_posted = true;
Serial.println("Sending finished");
vTaskDelay(1000);
}
}
In my code I am receiving a "Wifi is not declared in this scope" error when compiling to a NodeMCU board. The code has some customization, but regarding the WiFi and lines where it calls Wifi functions, it has the same structure to the source code.
The source code compiles flawless, what let me think that there is no issue with libraries or any kind of updates. I already reviewed my code many times and don't get the error.
Here follows the complete compiling error:
C:\Users\Administrator\Documents\Arduino\teste_watsoniot\teste_watsoniot.ino:
In function 'void setup()':
teste_watsoniot:65:14: error: 'Wifi' was not declared in this scope
if (strcmp(Wifi.SSID().c_str(), ssid) != 0) {
^
teste_watsoniot:73:59: error: 'Wifi' was not declared in this scope
Serial.print("Connected, IP address: ");
Serial.println(Wifi.localIP());
^
exit status 1 'Wifi' was not declared in this scope
Here is the code:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <SimpleTimer.h>
#include "DHT.h"
/* Wi-Fi Information */
const char* ssid = "xxx";
const char* password = "xxx";
/* Watson Configurations */
#define DEVICE_TYPE "xxx"
#define DEVICE_ID "xxx"
#define ORG "xxx"
#define TOKEN "xxx" // this authentication token given with API key
char server[] = ORG ".messaging.internetofthings.ibmcloud.com";
char topic[] = "iot-2/evt/status/fmt/json"; //"iot-2/type/xxx/id/xxx/evt/1-anl/fmt/json"; // customize type and ID
char authMethod[] = "use-token-auth";
//char authMeth[] = "xxx"; // here a API key
char token[] = TOKEN;
char clientID[] = "d:" ORG ":" DEVICE_TYPE ":" DEVICE_ID;
/* String to send data */
String Str1 = "hum";
String Str2 = "temp";
String Str3 = "ldrValue1";
String Str4 = "soilValue2";
/* Start Wi-Fi */
WiFiClientSecure wifiClient;
PubSubClient client(server, 1833, wifiClient);
/* TIMER */
SimpleTimer timer;
/* DHT22*/
#define DHTPIN D3
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
float hum = 0;
float temp = 0;
/* Soil Moister and LDR */
int sensorPin = A0; // analog input for both sensors
int enable1 = D1; // enable reading Sensor 1
int enable2 = D2; // enable reading Sensor 2
int ldrValue1 = 0;
int soilValue2 = 0;
void setup()
{
Serial.begin(115200); Serial.println();
Serial.print("Conectando na rede "); Serial.print(ssid);
if (strcmp(Wifi.SSID().c_str(), ssid) != 0) {
WiFi.begin(ssid, password);
}
while (WiFi.status() != WL_CONNECTED) {
delay (500);
Serial.print (".");
}
Serial.println("");
Serial.print("Connected, IP address: "); Serial.println(Wifi.localIP());
timer.setInterval(1000L, getDhtData);
pinMode(enable1, OUTPUT);
pinMode(enable2, OUTPUT);
dht.begin();
}
/* Send to cloud */
void enviaDado(float dado1,float dado2, float dado3, float dado4){
String payload = "{\"d\":{\"" + Str1 + "\":";
payload += dado1;
payload += ", \"" + Str2 + "\":";
payload += dado2;
payload += ", \"" + Str3 + "\":";
payload += dado3;
payload += ", \"" + Str4 + "\":";
payload += dado4;
payload += "}}";
Serial.print("Sending payload: ");
Serial.println(payload);
//__ Envia o dado
if (client.publish(topic, (char*) payload.c_str())) {
Serial.println("Publish ok");
} else {
Serial.println("Publish failed");
}
}
void loop()
{
// Sensor DHT22
getDhtData();
// Sensor 1 LDR
digitalWrite(enable1, HIGH);
ldrValue1 = analogRead(sensorPin);
ldrValue1 = constrain(ldrValue1, 300, 850);
ldrValue1 = map(ldrValue1, 300, 850, 0, 1023);
Serial.print("Light intensity: ");
Serial.println(ldrValue1);
digitalWrite(enable1, LOW);
delay(500);
// Sensor 2 SOIL MOISTURE
digitalWrite(enable2, HIGH);
delay(500);
soilValue2 = analogRead(sensorPin);
soilValue2 = constrain(soilValue2, 300, 0);
soilValue2 = map(soilValue2, 300, 0, 0, 100);
Serial.print("Soil moisture: ");
Serial.println(soilValue2);
Serial.println();
delay(500);
digitalWrite(enable2, LOW);
displayData();
delay(2000); // delay for getting DHT22 data
timer.run(); // Initiates SimpleTimer
}
/* Get DHT data */
void getDhtData(void)
{
float tempIni = temp;
float humIni = hum;
temp = dht.readTemperature();
hum = dht.readHumidity();
if (isnan(hum) || isnan(temp)) // Check if any reads failed and exit early (to try again).
{
Serial.println("Failed to read from DHT sensor!");
temp = tempIni;
hum = humIni;
return;
}
}
/* display DHT data */
void displayData(void)
{
Serial.print(" Temperature: ");
Serial.print(temp);
Serial.print("oC Humidity: ");
Serial.print(hum);
Serial.println("%");
}
Here is the source code:
https://github.com/ibm-watson-iot/device-arduino/blob/master/samples/ESP8266MqttSecure/ESP8266MqttSecure.ino
I am trying integrate opus into my application, the encode and decode function returns positive value which means successfully, but the output audio can't play. Raw audio data can play as well.
Here is how I encode data. I use 4 bytes prefix to separate from each packet.
self.encoder = opus_encoder_create(24000, 1, OPUS_APPLICATION_VOIP, &opusError);
opus_encoder_ctl(self.encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND));
- (void) encodeBufferList:(AudioBufferList *)bufferList {
BOOL success = TPCircularBufferProduceBytes(_circularBuffer, bufferList->mBuffers[0].mData, bufferList->mBuffers[0].mDataByteSize);
if (!success) {
NSLog(#"insufficient space in circular buffer!");
}
if (!_encoding) {
_encoding = YES;
dispatch_async(self.processingQueue, ^{
[self startEncodingLoop];
});
}
}
-(void)startEncodingLoop
{
int32_t availableBytes = 0;
opus_int16 *data = (opus_int16*)TPCircularBufferTail(_circularBuffer, &availableBytes);
int availableSamples = availableBytes / _inputASBD.mBytesPerFrame;
/*!
* Use dynamic duration
*/
// int validSamples[6] = {2.5, 5, 10, 20, 40, 60}; // in milisecond
// int esample = validSamples[0] * self.sampleRate / 1000;
// for (int i = 0; i < 6; i++) {
// int32_t samp = validSamples[i] * self.sampleRate / 1000;
// if (availableSamples < samp) {
// break;
// }
// esample = samp;
// }
/*!
* Use 20ms
*/
int esample = 20 * self.sampleRate / 1000;
if (availableSamples < esample) {
/*!
* Out of data. Finish encoding
*/
self.encoding = NO;
[self.eDelegate didFinishEncode];
return;
}
// printf("raw input value for packet \n");
// for (int i = 0; i < esample * self.numberOfChannels; i++) {
// printf("%d :", data[i]);
// }
int returnValue = opus_encode(_encoder, data, esample, _encoderOutputBuffer, 1000);
TPCircularBufferConsume(_circularBuffer, esample * sizeof(opus_int16) * self.numberOfChannels);
// printf("output encode \n");
// for (int i = 0; i < returnValue; i++) {
// printf("%d :", _encoderOutputBuffer[i]);
// }
NSMutableData *outputData = [NSMutableData new];
NSError *error = nil;
if (returnValue <= 0) {
error = [OKUtilities errorForOpusErrorCode:returnValue];
}else {
[outputData appendBytes:_encoderOutputBuffer length:returnValue * sizeof(unsigned char)];
unsigned char int_field[4];
int_to_char(returnValue , int_field);
NSData *header = [NSData dataWithBytes:&int_field[0] length:4 * sizeof(unsigned char)];
if (self.eDelegate) {
[self.eDelegate didEncodeWithData:header];
}
}
if (self.eDelegate) {
[self.eDelegate didEncodeWithData:outputData];
}
[self startEncodingLoop];
}
And here is decode function:
self.decoder = opus_decoder_create(24000, 1, &opusError);
opus_decoder_ctl(self.decoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
opus_decoder_ctl(self.decoder, OPUS_SET_GAIN(10));
-(void)startParseData:(unsigned char*)data remainingLen:(int)len
{
if (len <= 0) {
[self.dDelegate didFinishDecode];
return;
}
int headLen = sizeof(unsigned char) * 4;
unsigned char h[4];
h[0] = data[0];
h[1] = data[1];
h[2] = data[2];
h[3] = data[3];
int packetLen = char_to_int(h);
data += headLen;
packetLen = packetLen * sizeof(unsigned char) * self.numberOfChannels;
[self decodePacket:data length:packetLen remainingLen:len - headLen];
}
-(void)decodePacket:(unsigned char*)inputData length:(int)len remainingLen:(int)rl
{
int bw = opus_packet_get_bandwidth(inputData); //TEST: return OPUS_BANDWIDTH_SUPERWIDEBAND here
int32_t decodedSamples = 0;
// int validSamples[6] = {2.5, 5, 10, 20, 40, 60}; // in milisecond
/*!
* Use 60ms
*/
int esample = 60 * self.sampleRate / 1000;
// printf("input decode \n");
// for (int i = 0; i < len; i++) {
// printf("%d :", inputData[i]);
// }
_decoderBufferLength = esample * self.numberOfChannels * sizeof(opus_int16);
int returnValue = opus_decode(_decoder, inputData, len, _outputBuffer, esample, 1);
if (returnValue < 0) {
NSError *error = [OKUtilities errorForOpusErrorCode:returnValue];
NSLog(#"decode error %#", error);
inputData += len;
[self startParseData:inputData remainingLen:rl - len];
return;
}
decodedSamples = returnValue;
NSUInteger length = decodedSamples * self.numberOfChannels;
// printf("raw decoded data \n");
// for (int i = 0; i < length; i++) {
// printf("%d :", _outputBuffer[i]);
// }
NSData *audioData = [NSData dataWithBytes:_outputBuffer length:length * sizeof(opus_int16)];
if (self.dDelegate) {
[self.dDelegate didDecodeData:audioData];
}
inputData += len;
[self startParseData:inputData remainingLen:rl - len];
}
Please help me to point out what I am missing. An example would be great.
I think the problem is on the decode side:
You pass 1 as the fec argument to opus_decode(). This asks the decoder to generate the full packet duration's worth of data from error correction data in the current packet. I don't see any lost packet tracking in your code, so 0 should be passed instead. With that change your input and output duration should match.
You configure the decoder for mono output, but later use self.numberOfChannels in length calculations. Those should match or you may get unexpected behaviour.
OPUS_SET_SIGNAL doesn't do anything in opus_decoder_ctl() but it will just return OPUS_UNIMPLEMENTED without affecting behaviour.
Opus packets can be up to 120 ms in duration, so your limit of 60 ms could fail to decode some streams. If you're only talking to your own app that won't cause a problem the way you've configured it, since libopus defaults to 20ms frames.
I found what the problem is. I have set the audio format is float kAudioFormatFlagIsPacked|kAudioFormatFlagIsFloat;. I should use opus_encode_float and opus_decode_float instead of opus_encode opus_decode.
As #Ralph says, we should use fec = 0 in opus_decode. Thanks to #Ralph.
One thing I notice is that you're treating the return value of opus_encode() as a number of samples encoded, when it's the number of bytes in the compressed packet. that means you're writing 50% or 75% garbage data from the end of _encoderOutputBuffer into your encoded stream.
Also make sure _encoderOutputBuffer has room for the hard-coded 1000 byte packet-length limit you're passing in.
As you can see from the code, within my callback I extract out the audio data and place it into NSData data, then send that off to another class to upload that to the server. This all works, meaning the server receives and plays the audio data. HOWEVER there is a clicking or tapping noise between the buffers. I am hoping someone might show me what is causing that and how it can be fixed.
I have read other related postings however they all seemed to refer to only using 1 buffer and that adding more was the fix but I am using 3 buffers and have tried adjusting that number which did not fix it
AQRecorder.mm
#include "AQRecorder.h"
RestClient * restClient;
NSData* data;
// ____________________________________________________________________________________
// Determine the size, in bytes, of a buffer necessary to represent the supplied number
// of seconds of audio data.
int AQRecorder::ComputeRecordBufferSize(const AudioStreamBasicDescription *format, float seconds)
{
int packets, frames, bytes = 0;
try {
frames = (int)ceil(seconds * format->mSampleRate);
if (format->mBytesPerFrame > 0)
bytes = frames * format->mBytesPerFrame;
else {
UInt32 maxPacketSize;
if (format->mBytesPerPacket > 0)
maxPacketSize = format->mBytesPerPacket; // constant packet size
else {
UInt32 propertySize = sizeof(maxPacketSize);
XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize,
&propertySize), "couldn't get queue's maximum output packet size");
}
if (format->mFramesPerPacket > 0)
packets = frames / format->mFramesPerPacket;
else
packets = frames; // worst-case scenario: 1 frame in a packet
if (packets == 0) // sanity check
packets = 1;
bytes = packets * maxPacketSize;
}
} catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
return 0;
}
return bytes;
}
// ____________________________________________________________________________________
// AudioQueue callback function, called when an input buffers has been filled.
void AQRecorder::MyInputBufferHandler( void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription* inPacketDesc)
{
AQRecorder *aqr = (AQRecorder *)inUserData;
try {
if (inNumPackets > 0) {
// write packets to file
// XThrowIfError(AudioFileWritePackets(aqr->mRecordFile, FALSE, inBuffer->mAudioDataByteSize,
// inPacketDesc, aqr->mRecordPacket, &inNumPackets, inBuffer->mAudioData),
// "AudioFileWritePackets failed");
aqr->mRecordPacket += inNumPackets;
// int numBytes = inBuffer->mAudioDataByteSize;
// SInt8 *testBuffer = (SInt8*)inBuffer->mAudioData;
//
// for (int i=0; i < numBytes; i++)
// {
// SInt8 currentData = testBuffer[i];
// printf("Current data in testbuffer is %d", currentData);
//
// NSData * temp = [NSData dataWithBytes:currentData length:sizeof(currentData)];
// }
data=[[NSData dataWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize]retain];
[restClient uploadAudioData:data url:nil];
}
// if we're not stopping, re-enqueue the buffer so that it gets filled again
if (aqr->IsRunning())
XThrowIfError(AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed");
} catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
AQRecorder::AQRecorder()
{
mIsRunning = false;
mRecordPacket = 0;
data = [[NSData alloc]init];
restClient = [[RestClient sharedManager]retain];
}
AQRecorder::~AQRecorder()
{
AudioQueueDispose(mQueue, TRUE);
AudioFileClose(mRecordFile);
if (mFileName){
CFRelease(mFileName);
}
[restClient release];
[data release];
}
// ____________________________________________________________________________________
// Copy a queue's encoder's magic cookie to an audio file.
void AQRecorder::CopyEncoderCookieToFile()
{
UInt32 propertySize;
// get the magic cookie, if any, from the converter
OSStatus err = AudioQueueGetPropertySize(mQueue, kAudioQueueProperty_MagicCookie, &propertySize);
// we can get a noErr result and also a propertySize == 0
// -- if the file format does support magic cookies, but this file doesn't have one.
if (err == noErr && propertySize > 0) {
Byte *magicCookie = new Byte[propertySize];
UInt32 magicCookieSize;
XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_MagicCookie, magicCookie, &propertySize), "get audio converter's magic cookie");
magicCookieSize = propertySize; // the converter lies and tell us the wrong size
// now set the magic cookie on the output file
UInt32 willEatTheCookie = false;
// the converter wants to give us one; will the file take it?
err = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie);
if (err == noErr && willEatTheCookie) {
err = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, magicCookieSize, magicCookie);
XThrowIfError(err, "set audio file's magic cookie");
}
delete[] magicCookie;
}
}
void AQRecorder::SetupAudioFormat(UInt32 inFormatID)
{
memset(&mRecordFormat, 0, sizeof(mRecordFormat));
UInt32 size = sizeof(mRecordFormat.mSampleRate);
XThrowIfError(AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareSampleRate,
&size,
&mRecordFormat.mSampleRate), "couldn't get hardware sample rate");
//override samplearate to 8k from device sample rate
mRecordFormat.mSampleRate = 8000.0;
size = sizeof(mRecordFormat.mChannelsPerFrame);
XThrowIfError(AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareInputNumberChannels,
&size,
&mRecordFormat.mChannelsPerFrame), "couldn't get input channel count");
// mRecordFormat.mChannelsPerFrame = 1;
mRecordFormat.mFormatID = inFormatID;
if (inFormatID == kAudioFormatLinearPCM)
{
// if we want pcm, default to signed 16-bit little-endian
mRecordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
mRecordFormat.mBitsPerChannel = 16;
mRecordFormat.mBytesPerPacket = mRecordFormat.mBytesPerFrame = (mRecordFormat.mBitsPerChannel / 8) * mRecordFormat.mChannelsPerFrame;
mRecordFormat.mFramesPerPacket = 1;
}
if (inFormatID == kAudioFormatULaw) {
NSLog(#"is ulaw");
mRecordFormat.mSampleRate = 8000.0;
mRecordFormat.mFormatFlags = 0;
mRecordFormat.mFramesPerPacket = 1;
mRecordFormat.mChannelsPerFrame = 1;
mRecordFormat.mBitsPerChannel = 8;
mRecordFormat.mBytesPerPacket = 1;
mRecordFormat.mBytesPerFrame = 1;
}
}
NSString * GetDocumentDirectory(void)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
void AQRecorder::StartRecord(CFStringRef inRecordFile)
{
int i, bufferByteSize;
UInt32 size;
CFURLRef url;
try {
mFileName = CFStringCreateCopy(kCFAllocatorDefault, inRecordFile);
// specify the recording format
SetupAudioFormat(kAudioFormatULaw /*kAudioFormatLinearPCM*/);
// create the queue
XThrowIfError(AudioQueueNewInput(
&mRecordFormat,
MyInputBufferHandler,
this /* userData */,
NULL /* run loop */, NULL /* run loop mode */,
0 /* flags */, &mQueue), "AudioQueueNewInput failed");
// get the record format back from the queue's audio converter --
// the file may require a more specific stream description than was necessary to create the encoder.
mRecordPacket = 0;
size = sizeof(mRecordFormat);
XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_StreamDescription,
&mRecordFormat, &size), "couldn't get queue's format");
NSString *basePath = GetDocumentDirectory();
NSString *recordFile = [basePath /*NSTemporaryDirectory()*/ stringByAppendingPathComponent: (NSString*)inRecordFile];
url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)recordFile, NULL);
// create the audio file
XThrowIfError(AudioFileCreateWithURL(url, kAudioFileCAFType, &mRecordFormat, kAudioFileFlags_EraseFile,
&mRecordFile), "AudioFileCreateWithURL failed");
CFRelease(url);
// copy the cookie first to give the file object as much info as we can about the data going in
// not necessary for pcm, but required for some compressed audio
CopyEncoderCookieToFile();
// allocate and enqueue buffers
bufferByteSize = ComputeRecordBufferSize(&mRecordFormat, kBufferDurationSeconds); // enough bytes for half a second
for (i = 0; i < kNumberRecordBuffers; ++i) {
XThrowIfError(AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers[i]),
"AudioQueueAllocateBuffer failed");
XThrowIfError(AudioQueueEnqueueBuffer(mQueue, mBuffers[i], 0, NULL),
"AudioQueueEnqueueBuffer failed");
}
// start the queue
mIsRunning = true;
XThrowIfError(AudioQueueStart(mQueue, NULL), "AudioQueueStart failed");
}
catch (CAXException &e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
catch (...) {
fprintf(stderr, "An unknown error occurred\n");
}
}
void AQRecorder::StopRecord()
{
// end recording
mIsRunning = false;
// XThrowIfError(AudioQueueReset(mQueue), "AudioQueueStop failed");
XThrowIfError(AudioQueueStop(mQueue, true), "AudioQueueStop failed");
// a codec may update its cookie at the end of an encoding session, so reapply it to the file now
CopyEncoderCookieToFile();
if (mFileName)
{
CFRelease(mFileName);
mFileName = NULL;
}
AudioQueueDispose(mQueue, true);
AudioFileClose(mRecordFile);
}
I changed my #define kBufferDurationSeconds from .5 to 5.0 and although the clicking is still there it is alot less noticeable.
Please if you have suggestions/answer still post as this is not a fix merely a work around thats somewhat better then before
I also tried to append data to data for a number of times prior to sending the data to the server. This also seems to have helped.